Blog

User blogs

Romain GEORGES

Tag cloud

POST #56RSpec : Follow the Mock Turtle

Ajouté par Romain GEORGES il y a plus de 5 ans

Imaginons un Composant logiciel qui doit executer des #action, un objet. Imaginons que comme il se doit, il loggue ses actions.
Allons-y de plus en plus élégamment (donc la première version c'est comme l'Australie en kangourou, il faut pas le faire !):

dans ./composant.rb :

class Composant 

  def initialize
    # nothing 
  end

 def action 
   # a process
   puts 'action made' 
 end

end

Voila la c'est bien sale !
si on veut changer de méthode de logging, on modifie la classe Composant, methode :action ! c'est mal !

Déja, reprennons sur de bonne base :
TDD, même BDD => Rspec !! Behaviour driven Dev..

$ sudo gem install rspec 

On va donc écrire des Spec dans ./composant.spec:

require 'composant.rb'
describe Composant do
  specify { described_class.class.should be_a_kind_of(Class) }
  specify { described_class.should equal(Composant) }
  subject { Composant::new }
  specify { subject.should respond_to :action }
end

A priori notre Composant devrai faire l'affaire

#rspec -fn composant.spec
Composant
  should be a kind of Class
  should equal Composant
  should respond to #action

Finished in 0.00138 seconds
3 examples, 0 failures
#

L'idéal est de créer une classe à part pour logguer, pour bien séparer les activités,
on va specifier une class de log Logutils dans ./logutils.spec

require 'logutils.rb'
describe Logutils do
  specify { described_class.class.should be_a_kind_of(Class) }
  specify { described_class.should equal(Logutils) }
  subject { Logutils::new }
  specify { subject.should respond_to :echo }
end

on lance rspec :

#rspec -fn logutils.spec                                              
/usr/local/lib/site_ruby/1.8/rubygems/custom_require.rb:36:in `gem_original_require': no such file to load -- logutils.rb (LoadError)
    from /usr/local/lib/site_ruby/1.8/rubygems/custom_require.rb:36:in `require'
    from /home/lecid/logutils.spec:1
    from /usr/lib/ruby/gems/1.8/gems/rspec-core-2.8.0/lib/rspec/core/configuration.rb:698:in `load'
    from /usr/lib/ruby/gems/1.8/gems/rspec-core-2.8.0/lib/rspec/core/configuration.rb:698:in `load_spec_files'
    from /usr/lib/ruby/gems/1.8/gems/rspec-core-2.8.0/lib/rspec/core/configuration.rb:698:in `map'
    from /usr/lib/ruby/gems/1.8/gems/rspec-core-2.8.0/lib/rspec/core/configuration.rb:698:in `load_spec_files'
    from /usr/lib/ruby/gems/1.8/gems/rspec-core-2.8.0/lib/rspec/core/command_line.rb:22:in `run'
    from /usr/lib/ruby/gems/1.8/gems/rspec-core-2.8.0/lib/rspec/core/runner.rb:80:in `run_in_process'
    from /usr/lib/ruby/gems/1.8/gems/rspec-core-2.8.0/lib/rspec/core/runner.rb:69:in `run'
    from /usr/lib/ruby/gems/1.8/gems/rspec-core-2.8.0/lib/rspec/core/runner.rb:10
    from /usr/bin/rspec:19
#

Normal, on va donc implémenter Logutils dans le fichier ./logutils.rb :

class Logutils
  def initialize
  end
  def echo(msg) 
    return true
  end
end

on lance rspec :

#rspec -fn logutils.spec                                              
Logutils
  should be a kind of Class
  should equal Logutils
  should respond to #echo

Finished in 0.00134 seconds
3 examples, 0 failures
#

Bon début.
Mais revenons à notre premier cas de conscience, utiliser un logguer séparé dans notre Composant, on spécifie dans ./composant.spec.

require 'composant.rb'
describe Composant do
  specify { described_class.class.should be_a_kind_of(Class) }
  specify { described_class.should equal(Composant) }
  subject { Composant::new }
  specify { subject.should respond_to :action }
  context "attribut @logger" do
    specify { subject.instance_variable_get(:@logger).should be_an_instance_of Logutils }
  end
end

on relance rspec

Composant
  should be a kind of Class
  should equal Composant
  should respond to #action
  attribut @logger
     (FAILED - 1)

Failures:

  1) Composant attribut @logger 
     Failure/Error: specify { subject.instance_variable_get(:@logger).should be_an_instance_of Logutils }
     NameError:
       uninitialized constant Logutils
     # ./composant.spec:8

Finished in 0.00194 seconds
4 examples, 1 failure

Failed examples:

rspec ./composant.spec:8 # Composant attribut @logger

Allons donc coder ça dans ./composant.rb

require 'logutils.rb'
class Composant 

  def initialize
    @logger = Logutils::new 
  end

 def action 
   # a process
   @logger.echo 'action made' 
 end

end

on relance rspec :

Composant
  should be a kind of Class
  should equal Composant
  should respond to #action
  attribut @logger
    should be an instance of Logutils
    should be true

Finished in 0.00165 seconds
4 examples, 0 failures

Bien !

Par contre @logger est obligratoirement un Logutils...
si on veut pour tester Composant sans alternative de code dans les deux classe, une seule solution le Mock Object
Ici, une bonne pratique s'impose, le découplage !!
il faut faire en sorte qu'un composant reçoive en paramètre d'initialisation un logger, encore mieux serait de faire une IOC ( inversion de contrôle), depuis une factory ou un gestionnaire de ressource (server d'application)

j'en viens directement au cas le plus élégant (avec détails et fioritures) :

d'abord les specs : ./logutils.spec (oui, elles sont précises !!)

require './logutils.rb'
describe Logutils do
  before :all do
    @logger = Logutils::new
    @agent_logger = Logutils::new('Agent')
  end

  specify { described_class.class.should be_a_kind_of(Class) }
  specify { described_class.should equal(Logutils) }
  context "#initialize" do
    context "Exceptions" do
      it "should not raise ArgumentError if running without any arguments" do
        lambda { Logutils::new }.should_not raise_error ArgumentError
      end
      it "should raise TypeError if running with one only non String argument" do
        lambda { Logutils::new(10)}.should raise_error TypeError
      end
      it "should not raise ArgumentError if running with one String argument" do
        lambda { Logutils::new('Agent')}.should_not raise_error ArgumentError
      end
      it "should raise ArgumentError if running with more than 1 argument" do
        lambda { Logutils::new('Agent','arg')}.should raise_error ArgumentError
      end
    end
    specify { @logger.should respond_to :echo }
    context "when running #new" do
      context "attribut @caller" do
        specify { @logger.instance_variable_get(:@caller).should eq('unknown') }
      end
      context "attribut @last_message" do
        specify { @logger.instance_variable_get(:@last_message).should eq('') }
      end
      context "attribut @last_args" do
        specify { @logger.instance_variable_get(:@last_args).should eq([]) }
      end
    end
    context "when running #new('Agent')"do
      context "attribut @caller" do
        specify { @agent_logger.instance_variable_get(:@caller).should eq('Agent') }
      end
      context "attribut @last_message" do
        specify { @agent_logger.instance_variable_get(:@last_message).should eq('') }
      end
      context "attribut @last_args" do
        specify { @agent_logger.instance_variable_get(:@last_args).should eq([]) }
      end
    end

  end

  context "#echo" do
    context "Exceptions" do
      subject { Logutils::new }
      it "should raise ArgumentError if running without any arguments" do
        lambda { subject.echo}.should raise_error ArgumentError 
      end
      it "should raise ArgumentError if running with one only non String argument" do
        lambda { subject.echo(10)}.should raise_error ArgumentError 
      end
      it "should raise ArgumentError if running with many arguments but not all Strings" do
        lambda { subject.echo('toto',10)}.should raise_error ArgumentError
      end
      it "should not raise ArgumentError if running with one String argument" do
        lambda { subject.echo('message')}.should_not raise_error ArgumentError
      end
      it "should not raise ArgumentError if running with many String only arguments" do
        lambda { subject.echo('message','arg')}.should_not raise_error ArgumentError
      end
    end
    context "when running #echo('message')" do
      specify { @logger.echo('message').should be_true }
      context "attribut @last_message" do 
        specify { @logger.instance_variable_get(:@last_message).should eq('message') }
      end
      context "attribut @last_args" do 
        specify { @logger.instance_variable_get(:@last_args).should eq([]) }
      end
    end
    context "when running #echo('message','arg1','arg2')" do
      specify { @logger.echo('message','arg1','arg2').should be_true }
      context "attribut @last_message" do 
        specify { @logger.instance_variable_get(:@last_message).should eq('message') }
      end
      context "attribut @last_args" do 
        specify { @logger.instance_variable_get(:@last_args).should eq(['arg1','arg2']) }
      end
    end
  end
end

pour le code qui match ces specs, dans ./logutils.rb

class Logutils

  def initialize (caller = nil)
    raise TypeError::new('Caller is not a String') unless caller.class == String or caller.nil?
    @caller = (caller.nil?)? 'unknown' : caller
    @last_message = String::new
    @last_args = Array::new
  end

  def echo (message,*args)
    raise ArgumentError::new('Message is not a String') unless message.class == String
    args.each do |item|
      raise ArgumentError::new("Arguments contains non String") unless item.class == String
    end
    @last_message = message
    @last_args = args
    puts "from #{@caller} send : #{message} with args : #{args.join(',')}" 
    return true
  end
end

Attention à l'execution de Rspec, cette class fait des 'puts', donc regardez bien la commande :

#rspec -fn -c test.spec -o test.txt; cat test.txt
from unknown send : message with args : 
from unknown send : message with args : arg
from unknown send : message with args : 
from unknown send : message with args : arg1,arg2

Logutils
  should be a kind of Class
  should equal Logutils
  #initialize
    should respond to #echo
    Exceptions
      should not raise ArgumentError if running without any arguments
      should raise TypeError if running with one only non String argument
      should not raise ArgumentError if running with one String argument
      should raise ArgumentError if running with more than 1 argument
    when running #new
      attribut @caller
        should eq "unknown" 
      attribut @last_message
        should eq "" 
      attribut @last_args
        should eq []
    when running #new('Agent')
      attribut @caller
        should eq "Agent" 
      attribut @last_message
        should eq "" 
      attribut @last_args
        should eq []
  #echo
    Exceptions
      should raise ArgumentError if running without any arguments
      should raise ArgumentError if running with one only non String argument
      should raise ArgumentError if running with many arguments but not all Strings
      should not raise ArgumentError if running with one String argument
      should not raise ArgumentError if running with many String only arguments
    when running #echo('message')
      should be true
      attribut @last_message
        should eq "message" 
      attribut @last_args
        should eq []
    when running #echo('message','arg1','arg2')
      should be true
      attribut @last_message
        should eq "message" 
      attribut @last_args
        should eq ["arg1", "arg2"]

Finished in 0.0533 seconds
24 examples, 0 failures

Ensuite, et c'est la que ça devient vraiment intéressant !!!
Les specs du squelette du Composant et l'usage du 'Mock', il s'agit d'un squelette de Composant, cette spec n'est exhaustive, il manque notamment les Exceptions

./composant.spec

describe Composant do
  before :all do
    @comp = Composant::new
    @comp_dummy = Composant::new(Logutils::new('Dummy'))
   end
  specify { described_class.class.should be_a_kind_of(Class) }
  specify { described_class.should equal(Composant) }
  subject { Composant::new }
  context "initialization" do
    context "when running #new" do
      context "attribut @logger" do
        specify { @comp.instance_variable_get(:@logger).should be_an_instance_of Logutils }
      end
    end
    context "when running #new(Logutils::new('Dummy'))" do
      context "attribut @logger" do
        specify { @comp_dummy.instance_variable_get(:@logger).should be_an_instance_of Logutils }
      end
    end
  end
  specify { subject.should respond_to :action }
  context "#action" do
    context "Exceptions" do

    end
    context "when running" do
      it "@logger should receive message :echo with arguments  'action',['arg1','arg2'] and return true" do
        @aFakeLogutils = mock(Logutils)
        @test = Composant::new(@aFakeLogutils)
        testexpectation = @aFakeLogutils.should_receive(:echo).with('action',['arg1','arg2']).and_return(true)
        @test.action('arg1','arg2')
      end
    end
  end
end

Remarque : il ne semble pas possible de faire le mock dans le before :all, je ne sais pas pourquoi, je trouve ça dommage.

passons donc au code :

./composant.rb

class Composant

  def initialize (alogger = Logutils::new(self.class.to_s))
    @logger = alogger
  end

  def action(*args)
    args.each do |item|
      raise ArgumentError::new("Arguments contains non String") unless item.class == String
    end
    return @logger.send(:echo,__method__.to_s,args) unless @logger.nil?
    # do process #                                                                                                                                                          
  end

end

Les mesquins ne manquerons pas de noter la débauche d'énergie pour ça ! mais cette exemple doit-être situé dans un contexte plus large, la démarche doit être généralisée et cette fois-ci, le travail et le formalisme, fait une bonne fois pour toute, n'est plus a faire.

si on execute Rspec :

#rspec -fn -c test.spec -o test.txt; cat test.txt
Composant
  should be a kind of Class
  should equal Composant
  should respond to #action
  initialization
    when running #new
      attribut @logger
        should be an instance of Logutils
    when running #new(Logutils::new('Dummy'))
      attribut @logger
        should be an instance of Logutils
  #action
    when running
      @logger should receive message :echo with arguments  'action',['arg1','arg2'] and return true

Finished in 0.01601 seconds
6 examples, 0 failures

And "Voila" !!

POST #55ActiveLDAP + FreeBSD + [net|ruby|meuh|coin]-ldap

Ajouté par Romain GEORGES il y a plus de 5 ans

Après 3h de souffrance, je vous donne ça comme ça vient.

N'utilisez pas ruby-ldap sur FreeBSD 7.X ou 8.X, ni en port, ni en gem, sinon ça SEGFAULT gentillement !

en gros d'urgence :

$ sudo zsh
# gem uni ruby-ldap
# pkg_delete ruby18-ldap-X.X.X-X
# gem ins net-ldap
 

POST #54virtualhosts SSL sur Apache2

Ajouté par Romain GEORGES il y a plus de 5 ans

Contexte

Le support SNI, soit Server Name Indication, dans les librairies SSL. est normalement intégré sur ma version d'OpenSSL (> 0.9.8k),

Principe

Lorsqu'un client se connecte au serveur web en HTTPS, la connexion SSL est établie avant de connaitre le nom d'hôte cible, donc le certificat SSL qui est envoyé pour établir la connexion est toujours celui par défaut, j'avais appris à vivre avec, en gros le même certificats, pour tout le monde, et régime wilcard.ultragreen.net SSL pour tout le monde avec un alias en ultragreen.net qui va avec.

Défaillance régulière

A chaque mise à jours de la chaine Web, galère de config, mais jusqu'à présent je m'en suis toujours tiré, .... pas là !
Pbm de compatibilité, donc impossible de faire des vhosts SSL Nommés tout cours, layer SSL qui fait joliement Segfaulter mon httpd, même avec le même certificat,
D'habitude, entre deux versions d'OpenSSL et d'Apache2, un coup ça marchait, un coup ça marchait pas mais on reconfigurant ça marchait, un coups il fallait mettre les primitives de certificats en global context, un coup dans les Vhost, bref les updates étaient de vraies punitions.
Cette fois-ci j'ai testé TOUS mes Workarrounds habituels, mais rien, avant de baisser les bras, j'ai googlisé, et j'ai découvert qu'il y avait une autre voie que le coté obscur SSL.

Solution, mod-gnuTLS

En plus de marcher, correctement, c'est BEAUCOUP moins verbeux que mod_SSL+OpenSSL, ça permet du VRAI vhost SSL nommé sur une IP avec plusieurs certificats SSL, qui "marche-pour-de-vrai-c'est-pas-des-blagues-qu'en-fait-c'était-possible".

Migration

la migration sous mod_gnutls pour tous les vhosts sur les deux noeuds Web, c'est fait en moins d'une heure

Documentation

http://wiki.gandi.net/fr/ssl/multiplecertononehostipport

POST #53Pourquoi je ne fait plus de Perl

Ajouté par Romain GEORGES il y a environ 6 ans

Imaginez un hash de données structurés servants à être sérialisées en XML pour être fournies par un Web service
On met en place un filtrage/routage pour déterminer les Web services vers lesquels on doit communiquer les données structurés.
Je passe le fait que le SOAP et Perl, c'est déjà de la science fiction, que le SOAP Document, c'est pas possible avec SOAP::Lite, WSDL j'oublie aussi...
Je passe aussi la faiblesse du support XML/Schema .....

J'en viens au cas bien précis :

on veut créer une méthode qui renvoie la valeur d'un élément si il existe, pour des fins de filtrages routages

tel que en Xpath, la recherche :

/racine/noeud/feuille[item[$valeur]

code de simulation,

%params = {};
$valeur = 'test';
return $params{'racine'}{'noeud'}{'feuille'}{$valeur} if defined($params{'racine'}{'noeud'}{'feuille'}{$valeur});

Et la, on pourrait croire que le defined ne va pas altérer mon hash, et bien si !!

toute l'arborescence vers feuille[$valeur] existe !!

$ perl -e 'use Data::Dumper; %params = (); $valeur = 'test'; print Dumper %params; print "tutu" if defined($params{'racine'}{'noeud'}{'feuille'}{$valeur}); print Dumper %params' 
$VAR1 = 'racine';
$VAR2 = {
          'noeud' => {
                       'feuille' => {}
                     }
        };

si on passe le hash par référence, et tout le monde fait ça chez les mongueurs, et bien on vient de fusiller son hash.

on va donc sérialiser :


<racine>
  <noeud>
   <feuille>
   </feuille>
  <noeud>
<racine>

Merci Perl !!

POST #52Fontaine je ne boirai jamais de ton eau

Ajouté par Romain GEORGES il y a environ 6 ans

Je vais peut-être, soit, au mieux, surprendre certains qui me connaissent bien, au pire, choquer d'autres, mais je java-ise dure, ces derniers temps.

J2EE 5 et 6 on eut raison de mes retenues, jRuby me donne une porte d'entrée Royale.

Et SOA et les architectures avec ESB et BPM m'imposent le chemin.

Il serai difficile de nier l'intérêt que je porte au projet Redmine et la on trouve la croisée des chemins sous le même caillou :

  • Un poisson (Glassfish) :

http://en.wikipedia.org/wiki/GlassFish

Un Bus à l'impériale (OpenESB) séduisant (avec cependant des craintes sur les objectifs d'Oracle pour son futur) :

http://en.wikipedia.org/wiki/Open_ESB

  • et une mine de Ruby, de jRuby même :

http://musingsofaprogrammingaddict.blogspot.com/2008/11/redmine-on-jruby-and-glassfish.html

POST #51Agile et fainéant

Ajouté par Romain GEORGES il y a environ 6 ans

Encore du troll, dirons certains, mais je sais trouver les points forts et les points faibles des deux.

Mais de toute façon, Java reste et restera plus verbeux que Ruby, c'est un fait ! même dans la lecture !!!

POST #48FreeNode : Pro scientologie ??

Ajouté par Romain GEORGES il y a plus de 6 ans

Il y a 20 minutes, je me connecte avec le client Web Irc de FreeNODE, pour regarder si il est bien ( il est mieux que CGI-IRC, il gère la déconnexion IRC en cas de close du navigateur en Javascript, je vais modifier mon client, quand j'aurai le temps)

la curiosité, sûrement me fait faire un /links sur FreeNODE, et je découvre alors une liste de serveurs qui portent des noms d'auteurs connus de SF, je me dis que c'est amusant jusqu'à trouver, HUBBARD ....

Google, Wikipédia, je vérifie qu'il s'agit bien de ce Ron Hubbard effectivement auteur de SF, mais aussi le fondateur de l'église de Scientologie ...

[17:00] niven.freenode.net bear.freenode.net 1 Corvallis, OR, USA
[17:00] verne.freenode.net bear.freenode.net 1 Newark, NJ, US
[17:00] brown.freenode.net bear.freenode.net 1 Madison, WI, US
[17:00] barjavel.freenode.net bear.freenode.net 1 Paris, FR
[17:00] lindbohm.freenode.net bear.freenode.net 1 Stockholm, Sweden
[17:00] sendak.freenode.net bear.freenode.net 1 Vilnius, Lithuania, EU
[17:00] jordan.freenode.net bear.freenode.net 1 Evry, FR
[17:00] gibson.freenode.net bear.freenode.net 1 Oslo, Norway
[17:00] leguin.freenode.net bear.freenode.net 1 Ume?, SE, EU
[17:00] bartol.freenode.net bear.freenode.net 1 Luxembourg, Luxembourg
[17:00] holmes.freenode.net bear.freenode.net 1 London, UK
[17:00] asimov.freenode.net bear.freenode.net 1 TX, USA
[17:00] card.freenode.net bear.freenode.net 1 Washington, DC, USA
[17:00] roddenberry.freenode.net bear.freenode.net 1 Brisbane, AU
[17:00] services. bear.freenode.net 1 Atheme IRC Services
[17:00] stross.freenode.net bear.freenode.net 1 Corvallis, OR, USA
[17:00] pratchett.freenode.net bear.freenode.net 1 Rennes, France
[17:00] kornbluth.freenode.net bear.freenode.net 1 Frankfurt, Germany

  • hubbard.freenode.net bear.freenode.net 1 Pittsburgh, PA, US*

[17:00] calvino.freenode.net bear.freenode.net 1 Milan, IT
[17:00] anthony.freenode.net bear.freenode.net 1 Irvine, CA, USA
[17:00] zelazny.freenode.net bear.freenode.net 1 Corvallis, OR, USA
[17:00] bear.freenode.net bear.freenode.net 0 London, England
[17:00] * End of /LINKS list.

Faute de goût ou militantisme ?

POST #47Redmine : I18n, Rails, ou Redmine ? (1 commentaire)

Ajouté par Romain GEORGES il y a plus de 6 ans

L'internationnalisation, un sujet compliqué ? oui, il faut croire.

J'installe , un de mes gems en local sur le serveur Rails, pour faire des tests de déploiement sur le serveur de Gems d'Ultragreen.
Une dépendance volontaire de mon gem, I18n.

Et la après 20 minute, je m'aperçois que le helper application de Redmine:

def authoring(created, author, options={})
    l(options[:label] || :label_added_time_by, :author => link_to_user(author), :age => time_tag(created))
end

Se prend les pieds dans le tapis.

genre "Ajouté par {{author}} il y a {{age}}"

On sent bien le problème finalement.

la méthode l()

Cette méthode est la méthode d'interpretation des chaînes I18n.
Et, cette version n'interpole pas les tokens !!
J'ai viré le gem I18n, touch tmp/restart.txt et ça remarche !!
Alors le coupable à l'air simple, le package I18n !!
Sauf que, le fait qu'il ne marche pas sur Redmine, vient de :

  1. Redmine qui surcharge l() pour lui faire interpoler et qui se fait piéger par le gestionnaire de dépendance de Rails 2.X (a tester avec le Bundler Rails 3) ?
  2. Redmine qui embarque en vendor/gems un I18n non officiel avec la dite surcharge ?
  3. Rails 2.X qui possède une version inline de I18n pas officielle ?
  4. I18n en gem qui est pas "officiel" ?

D'un point de vue général, il y a un truc pas uniforme dans la chaîne, et le package I18n gem à l'air "Fénéant".
Bref, pas drôle !! une new version de I18n iso avec la version utile pour Rails s'impose avec un joli Bundler Rails 3

S'il vous plais, portez Redmine sous Rails 3 !!

POST #45Comment-ça tu bosses pas le Week End ?

Ajouté par Romain GEORGES il y a plus de 6 ans

résoudre un problème avec élégance, une des vertus de Ruby :

Le temps Business, qu'est-ce que c'est ?

c'est le temps effectivement chargeable !!

Genre ton chef te demande un truc, et en suite il te dit, tu pense qu'il te faut combien de temps ?

Toi innocent, tu lui réponds, "ben 4 jours..", et on est vendredi matin

Dans la tête du chef, vendredi + 4 ====> okai, tu me le mail lundi soir !!

Ou alors tu peux lui dire, rentre ou laisse moi rentrer mes charges dans mon superbe outils de timesheeting qui implémente :

business_time : https://github.com/bokmann/business_time

avec des jolies syntaxes du genre :

1.business_hour.from_now
4.business_hours.from_now
8.business_hours.from_now

1.business_hour.ago
4.business_hours.ago
8.business_hours.ago

1.business_day.from_now
4.business_days.from_now
8.business_days.from_now

1 2 3 4 5 ... 8 (21-30/74)

Formats disponibles : Atom RSS