How to change the default execution order of feature files in Cucumber

When you are running all your Cucumber feature scripts from Rake, often you would want to change the default execution order. Currently, cucumber loads all the feature files in ‘features/’ directory in alphabetical order and executes them.

This can be very annoying, specially when you want to execute certain critical features run before running the other not so critical features. Cucumber does not have any provision for feature ordering. But with some minor hacks, you can achieve this.

1st, you need to give each of your features a certain ‘weight’ in a yml file:

features/vcc_admin_create_test.feature: 80
features/transferred_acd_call.feature: 9
features/supervisor_create_test.feature: 8
features/super_force_signout_test.feature: 7
features/simple_queue_call_test.feature: 6
features/queue_delivered_count_test.feature : 67
features/queue_create_test.feature : 13
features/queue_call_treatment_test.feature : 12
features/email_status_monitor_test.feature : 11
features/email_forward.feature : 10
features/dynamic_and_static_number_test.feature : 9
features/agent_profile_change_test.feature : 8
features/agent_email_receiver_test.feature : 7
features/agent_create_test.feature : 6
features/agent_acd_state_test.feature : 5
features/agent_acd_call_duration_test.feature : 4
features/agent_acd_call_count_test.feature : 3
features/admin_call_distribution_policy_test.feature : 2
features/CC_create_test.feature : 1

next, update your env.rb file for adding a hook:

# Overrides the method +method_name+ in +obj+ with the passed block
def override_method(obj, method_name, &block)
  # Get the singleton class/eigenclass for 'obj'
  klass = class <<obj; self; end
 
  # Undefine the old method (using 'send' since 'undef_method' is protected)
  klass.send(:undef_method, method_name)
 
  # Create the new method
  klass.send(:define_method, method_name, block)
end
 
 
  
def get_weight(x)
  weights = YAML::load(File.open('order.yml')) #this is expensive but is done only on rake start
  weight = weights[x]
  if weight == nil or weight == ''
    weight = '100' #this is because we want to run the newly added tests first.
  end
  return weight.to_i()
end
 
def sort_according_to_weights(features)
  return features.sort { |x,y|  get_weight(y)  &lt;=&gt; get_weight(x)}
end
 
 
AfterConfiguration do |configuration|
  puts "\n\n\n *****  config: #{configuration} "
  featurefiles =  configuration.feature_files
 
  override_method(configuration, :feature_files) {
    puts "overriding the old featurefiles according to their weight"
    sorted_files = sort_according_to_weights(featurefiles);   
    sorted_files
  }
 
    puts "\n\n *************************** features will be executed in following order: \n"
    for i in configuration.feature_files
      puts "#{i} : weight: #{get_weight(i)}"
    end
end

After Cucumber loads the feature files from the directory, it calls “AfterConfiguration” before it starts executing the features. Sadly the configuration.feature_files is not an array which you can just sort. Its a method ! But with ruby being a dynamic language, you can push new method definition into the configuration instance so that it returns the features according to the order you specified !

Integrate Sipper with Cucumber for functional automation

Sipper is a good framework for doing functional automation of any sip call flow. Cucumber is a great tool for writing your functional test cases in plain text and execute them. Cucumber is mostly used for doing web automation with other tools like Watir. But now a days, VOIP meets Web now and then, And you are left with the challenge of integrating your web automation with your sip automation. For example, you want to automate “When a call comes to my sip phone, I want to see a javascript popup in my screen

Sipper comes with its testing/assertion/validation framework. First things first, we need to drop them. We are only interested into the Sip Stack and Call abstraction framework provided by sipper. We will do the assertion from our Cucumber framework.

First of all, you need to be able to run sipper in standalone mode rather than using the ‘rake’ provided by sipper framework. You need to bootstrap sipper framework from your ruby code. You can do that with the following code (usually you would want to put it in your support/env.rb configuration file for cucumber) :

require 'sipper'
require 'sipper_configurator'</code>

cf = File.join("C:/dev/projects/myproject/qa/sipper_tests/cucumber_tests/", "config", "sipper.cfg")
SipperConfigurator.load_yaml_file(cf)

@sipper = SIP::Sipper.new()
@sipper.start

Now that you have started sipper, you can register any controller in it from your cucumber steps. Lets say we have a step like this:

   Then I start customer's phone

You would want to start the controller that ‘REGISTER’ for the customer’s phone and wait for any incoming call.

Then /^I start customer's phone$/ do
  puts "Going to start Controller: CallReceiverCustomerController"
  @customer_controller = @sipper.start_named_controller("CallReceiverCustomerController")
  sleep 3
end

I have extended the standard sipper controller so that I can call it and control it from any ruby application. Basically this will act as a SIP phone and provide me the nice API’s

require 'sip_test_driver_controller'

# NOTE: Extending Core Sipper controller to provide programmable API to simple RUBY client. The ruby client 
# can directly call this controller to control the call flows.

class CommonCallController   < SIP::SipTestDriverController 

  # change the directive below to true to enable transaction usage.
  # If you do that then make sure that your controller is also 
  # transaction aware. i.e does not try send ACK to non-2xx responses,
  # does not send 100 Trying response etc.

  transaction_usage :use_transactions=>false

  # change the directive below to true to start after loading.
  start_on_load false


  def initialize    
    logd('CallReceiverController: Controller created')
    clear_states()
  end

  def clear_states()
    @registered = false
    @current_session = nil
    @call_ended = false
    @got_invite=false
  end

  def specified_transport    
    log " specified_transport #{get_local_sipper_ip()} : #{get_local_sipper_port()}"
    [get_local_sipper_ip(),get_local_sipper_port()]
  end  
  
  def start
    if required_registration
      register()
    end
    
    return self 
  end


  def register()
      log "doing registration with " + get_line_port
      session = create_udp_session(SipperConfigurator[:DefaultRIP], SipperConfigurator[:DefaultRP])
           r = session.create_initial_request("REGISTER", "sip:" + SipperConfigurator[:BroadworksDomain] ,
           :from=> "sip:"+ get_line_port, :to=>"sip:"+ get_line_port,
           :contact=> "sip:" + get_line_port_without_domain + "@"+ get_local_sipper_ip() +":"+get_local_sipper_port().to_s(),
           :expires=>"300",
           :cseq=>"1 REGISTER",
           :p_session_record=>"msg-info")
      r.contact.q="0.9"
      session = create_udp_session(SipperConfigurator[:DefaultRIP], SipperConfigurator[:DefaultRP])
      session.send(r)
      @current_session = session
      log "---------session: #{session}"
  end

  def required_registration
    true
  end
  
  def on_success_res_for_register(s)
    @current_session = s
    log "@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@ on_success_res_for_register"
    @registered = true
    s.invalidate(true);
  end

  def on_success_res(s)
     @current_session = s
    log '@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@ on_success_res ->'
    @registered = true
    s.invalidate(true);
 end
 
  def on_failure_res(session)
    sleep 10
    log "on_failure_res error code: #{session.iresponse.code}"
    puts "\n -----------------------------****************  #{caller}"
    if session.iresponse.code == 401
     r = session.create_request_with_response_to_challenge(session.iresponse.www_authenticate, false,get_auth_id, get_auth_password)     
     session.send r
     session[:auth] = r.authorization
    end
  end

  
  def is_ringing() 
    
      return @got_invite
    
  end
  
  #for the first invite it will send 180, for all other re-invite, it will send 200
  def on_invite(session)
    
    if !session['invite']
      log "got invite------------"
      @current_session = session
      log 'got call from Queue '
      session.respond_with(100)
      session.respond_with(180)
      session['invite']=1
      @got_invite=true
    else
      log "got re invite------------"
      log 'CallReceiverController:  got re-invite'
      if @accept_call         
        session.respond_with(200)      
       end
    end    
  end
  
  
  def make_call(uri)
    log "make_call : #{uri}"
    session = create_udp_session('10.0.0.27', 5060)
    session.request_with('INVITE', uri)
    #@current_session = session
  end
  
  def accept_call()
    log "accept_call #{@current_session}"
    @accept_call=true
    @current_session.respond_with(200)  
  end
  
  def end_call()
    log "ending call #{@current_session}"
    if @current_session
      log 'sending bye'
      @current_session.rp = SipperConfigurator[:DefaultRP]
      @current_session.request_with('BYE')
    end
  end
  
  def on_bye(session)
    log "*******************got bye"
    @call_ended = true
    session.respond_with(200)
    session.invalidate(true)
  end

  def is_call_ended()
    return @call_ended
  end
  
  def last_session_state
    @current_session.last_state
  end
  
  def get_local_sipper_port
    SipperConfigurator[:LocalSipperPort][4]
  end
  protected :get_local_sipper_port
  
  def get_local_sipper_ip
    SipperConfigurator[:LocalSipperIP]
  end  
  protected :get_local_sipper_ip

  def get_line_port
    SipperConfigurator[:AgentLinePort]
  end
  protected :get_line_port

  def controller_name
    self.class.name
  end
  
  def get_line_port_without_domain
    SipperConfigurator[:AgentLinePortWithoutDomain].to_s
  end
  protected :get_line_port_without_domain

  def get_auth_password
    SipperConfigurator[:CustomerAuthenticationPassword]
  end
  protected :get_auth_password
  
  def get_auth_id
    SipperConfigurator[:CustomerAuthenticationUserId]
  end
  protected :get_auth_id

  def log(argument) 
     puts  controller_name + " : " + argument
  end
end

Our CallReceiverCustomerController is a simple subclass of the CommonCallController to provide the SIP Registration Details so that it can register to any sip provider (in our case Broadworks).

require 'common_call_controller'

class CallReceiverCustomerController  < CommonCallController 


  def initialize
    puts "initialize"
    super()
    log 'CallReceiverControllerAgent: Controller created'
  end

  def get_local_sipper_port
    SipperConfigurator[:LocalSipperPort][6]
  end
  protected :get_local_sipper_port
  
  def get_local_sipper_ip
    SipperConfigurator[:LocalSipperIP]
  end  
  protected :get_local_sipper_ip

  def get_line_port
    SipperConfigurator[:AgentLinePort]
  end
  protected :get_line_port

  def controller_name
    self.class.name
  end
  
  def get_line_port_without_domain
    SipperConfigurator[:AgentLinePortWithoutDomain].to_s
  end
  protected :get_line_port_without_domain

  def get_auth_password
    SipperConfigurator[:AgentAuthenticationPassword]
  end
  protected :get_auth_password
  
  def get_auth_id
    SipperConfigurator[:AgentAuthenticationUserId]
  end
  protected :get_auth_id

end

Now the rest is simple, you can add cucumber steps for like this :

      Then customer Should get Alerting on his phone
      Then customer waits for 10 seconds
      Then customer accepts the phone
      Then customer waits for 20 seconds
      Then customer hangups the phone

and the steps definitions like this:

Then /^customer accepts the Call$/ do
puts "Agent is accepting the Call"
@customer_controller.accept_call()
end

Then /^customer hangs up the Call$/ do
puts "Agent is hanging up the Call"
@customer_controller.end_call()
end

Then /^customer should get alerting on his phone$/ do
i = 0
#wait for the ringing event to come
while ! @customer_controller.is_ringing() && i < 10
sleep 1
i += 1
end

if !@customer_controller.is_ringing()
raise "Agent didn't get any incoming call"
end
end

There you go. The beauty of this approach is that now, even your product owner can write test steps without having any clue of SIP / SIPPER. This is just plain english!

The "CommonCallController" I wrote is neither perfect not elegant. It uses instance variables to store the call state which can get wild for unexpected call flows. But It can easily be modified to suit your need. For me, it does its job for simple call flows.

There are plenty of documents on the web about how to use cucumber with Watir to automate webapplication testing..so I am not gonna talk about that.

Merge Sort implementation in Erlang

What else can you do in a dull Saturday evening 😉

-module (sorting).
-compile (export_all).

merge_sort([]) -> [];
merge_sort(L) ->
    {L1 , L2} = split(L),
    merge(merge_sort(L1),merge_sort(L2)).

merge(X , Y) ->
     merge(X,Y,[]).

merge([], [] , X) -> X;
merge([] , S2, X) -> X ++ S2;
merge(S1 , [], X) -> X ++ S1;
merge([H1 | T1] , [H2 | T2], X) when H1 >= H2 ->
    merge( [H1 | T1] , T2 , append(X,[H2])); %tail recursion
merge([H1 | T1] , [H2 | T2], X) ->
     merge( T1 , [H2 | T2], append(X,[H1])). %tail recursion 

append(X , Y) ->
      X ++ Y. % appending two list ! how cool!

split(L) ->
    split(L , {[] , []}).

split([] , {X , Y}) -> { X , Y };
split([H|T] , {X , Y}) ->
    split( T , { Y , append(X , [H]) }). %tail recursion

Resolving Spring Circular Reference Problem with proxied class

Have you ever run into a problem like this ?

"Error creating bean with name 'beanA': Bean with name 'beanA' has been injected into other beans (beanB) in its raw version as part of a circular reference, but has eventually been wrapped. This means that said other beans do not use the final version of the bean. This is often the result of over-eager type matching - consider using 'getBeanNamesOfType' with the 'allowEagerInit' flag turned off, for example."

Let me first explain why this happens:

You have a circular dependency between your BeanA & BeanB. In such cases, if one of your bean does not have any AOP applied to it (transaction / audit-trail etc) , spring behaves fine. However, if both of your beans are proxied or in other word, has AOP advice attached to it (mostly transaction advice) , you have a problem. This is because spring initially injects the raw version of your bean beanA in the other bean beanB but later adds Aspect into it. So If your beanA has any aspect (transaction / audit-trail ) , they will not be in action when beanB uses beanA. Spring does not allow this by default and gives you the exception.

In a perfect world and in a perfect project, you wouldn’t want circular dependency in your beans. But as we know, the world is not perfect and sometimes you just have to do it (obviously within same module).

How do I do it you ask? Fortunately you have a few options :

1. Set “allowEagerInit” to false in your application context.
2. Break the circular dependency for spring by not injecting one of the bean with application context and get the bean @ runtime by calling “applicationContext.getBean”
3. Write your own BeanFactory to allow circular dependency ( If you are ok with the side effects )

I am going to describe the 3rd method in details. With spring, its always very simple to add hook in necessary points. First write a class like this:

public class AllowRawInjectionDespiteWrappingXMLWebApplicationContext extends
		XmlWebApplicationContext {

	@Override
	protected void loadBeanDefinitions(DefaultListableBeanFactory beanFactory)
			throws IOException {
		beanFactory.setAllowRawInjectionDespiteWrapping(true);
		super.loadBeanDefinitions(beanFactory);
	}

	 
}

All I am doing here is changing the default value “allowRawInjectionDespiteWrapping” to true to let spring know I am OK with raw injection of my AOP beans.

Next, Change your default contextClass “XmlWebApplicationContext” class name which spring uses by adding the following in your web.xml file.

	<context-param>
	        <param-name>contextClass</param-name>
	        <param-value>com.myapp.AllowRawInjectionDespiteWrappingXMLWebApplicationContext</param-value>
	</context-param>

All that trouble to break a design rule (avoid circular dependency)..Fair enough !!

Exporting Spring Beans as JMX Mbeans

If you always want to stay on top of your production system and always want to know whats going on in your APP, you would want to configure JMX for your application.

First you need to start your JVM with the following parameters to enable any JMX client to connect to your system.

-Dcom.sun.management.jmxremote.port=8888 -Dcom.sun.management.jmxremote.authenticate=false -Dcom.sun.management.jmxremote.ssl=false

This is just done without any security but in production, you would want to add authentication.

Now, add these configuration for your spring APP to automatically expose your Spring Beans as MBeans if they have the right annotations.

	<bean id="exporter" class="org.springframework.jmx.export.MBeanExporter"
		lazy-init="false">
		<property name="autodetect" value="true"></property>
		<property name="namingStrategy" ref="namingStrategy"></property>
		<property name="assembler" ref="assembler"></property>		
		<property name="server" ref="mbeanServer"></property>		
	</bean>

	<bean id="assembler"
		class="org.springframework.jmx.export.assembler.MetadataMBeanInfoAssembler">
		<property name="attributeSource" ref="attributeSource" />
	</bean>

	<bean id="namingStrategy"
		class="org.springframework.jmx.export.naming.MetadataNamingStrategy">
		<property name="attributeSource" ref="attributeSource" />
	</bean>

	<bean id="mbeanServer" class="java.lang.management.ManagementFactory" factory-method="getPlatformMBeanServer"/>	

The ‘exporter’ will inspect all your defined Spring Beans in your configuration and see if the have Spring JMX annotations. Now you need to define your beans with Annotations.

@ManagedResource(objectName = "yourapp:name=log4jLevelChanger", description = "LOG4j Level Changer")
public class Log4jLevelChanger  {
	private static final Log log = LogFactory.getLog(Log4jLevelChanger.class);
	
	public void init() throws AttributeNotFoundException, InstanceNotFoundException, MBeanException, ReflectionException {
		ArrayList<MBeanServer> servers = MBeanServerFactory.findMBeanServer(null);
		for(MBeanServer server : servers) {
			String name = (String) server.getAttribute(MBeanServerDelegate.DELEGATE_NAME,
            "MBeanServerId");	
			
			log.info("found  server: " + name);
		}
	}
	
	/*
	 * (non-Javadoc)
	 * 
	 * @see
	 * com.vantage.callcenter.web.utils.Log4jLevelChanger#setLogLevel(java.lang
	 * .String, java.lang.String)
	 */
	@ManagedOperation
	@ManagedOperationParameters({
	@ManagedOperationParameter(description="Name of the Logger to configure", name="loggerName"),
	@ManagedOperationParameter(description="Level ofthe Logger to set", name="level")
	})
	public void setLogLevel(
			
			String loggerName, 
			
			String level) {
		if ("debug".equalsIgnoreCase(level)) {
			Logger.getLogger(loggerName).setLevel(Level.DEBUG);
		} else if ("info".equalsIgnoreCase(level)) {
			Logger.getLogger(loggerName).setLevel(Level.INFO);
		} else if ("error".equalsIgnoreCase(level)) {
			Logger.getLogger(loggerName).setLevel(Level.ERROR);
		} else if ("fatal".equalsIgnoreCase(level)) {
			Logger.getLogger(loggerName).setLevel(Level.FATAL);
		} else if ("warn".equalsIgnoreCase(level)) {
			Logger.getLogger(loggerName).setLevel(Level.WARN);
		}
	}

        //Read the log4j.properties to reconfigure the logging levels.
	@ManagedOperation
	public boolean refreshLogging() {
		try {
			Log4jConfigurer.initLogging("classpath:log4j.properties");			
			return true;
		} catch (FileNotFoundException e) {
			return false;
		}
	}
	
	
	@ManagedOperation
	@ManagedOperationParameters ({
		@ManagedOperationParameter(description="Name of the Logger to get the Level", name="loggerName")	
	})
	public String getLogLevel(
			
			String loggerName) {
		log.debug("getLogLevel -> loggerName: " + loggerName);
		Logger logger = Logger.getLogger(loggerName);
		
		if(logger != null) {
			if(logger.getLevel() != null) {
				return logger.getLevel().toString();
			}
		}
		
		return Logger.getRootLogger().getLevel().toString();
	}
}

and define it in your application-context.xml as a regular bean

<bean id="log4jLevelChanger"  class="com.test.Log4jLevelChanger"></bean>

Thats it! Now you can open any Standard JMX client (JConsole / VisualVM / JManage) to inspect the bean and changing log levels!

You can also add Mbean Annotations to any regular Bean that you wish to inspect at runtime. For example, you can add it to the bean that manages your threadpool to see its utilization at runtime, you can add it to any in memory cache to find out memory leaks. If you think closely, the possibilities can be endless!

Pragmatic Thinking & Learning

I just started reading the book “Pragmatic Thinking & Learning” published from the Pragmatic Programmers. Although I only read only the first few chapters so far, I am so excited about it that I thought I should write a line or two about it. If you are in Software development and want to learn the dynamics of software development and software development teams, you must read this book. This is by far the most impressive non technical book I have read so far.

Here is a snippet to give you some test:


Dreyfus at Work: Herding Racehorses and Racing Sheep :

In one of the Dreyfus studies, the researchers did exactly that. They took seasoned airline pilots and had them draw up a set of rules for the novices, representing their best practices. They did, and the novices were able to improve their performance based on those rules. But then they made the experts follow their own rules. It degraded their measured performance significantly.

– How true! Can you have the same set of rules for your top developer who is at “Expert/Proficient” level and the average developer who is @ “Novice/Beginner” level ? How can you explain intuition to the developer who needs a recipe? (If you don’t know what I am talking about, go read the book). This is one of the top challenges I have faced adopting SCRUM coming from a strict hierarchical team management. In SCRUM everyone from Novice to Expert seems to have the same “voice” and sometimes it just holds you back. But hey, the efficiency of a SCRUM team is often measured by the efficiency of the weakest node of the team! So thats fair enough 🙂

Where was I ???

Ok, its been long since I had my last blog post. Where was I? Well, I have been busy! Last few months have been the most happening months of my life.

First of all, I am a proud father of my 1 month old son now. Although I haven’t managed to stop watching cartoons and start acting like a dad yet, its really amazing how you feel!

Again, the product “Vantage Contact Center” that I have been working for last 1.5 years is in market now!

The product is built on top of Broadwork’s 3rd party call control api (OCI-P / CCC2) and Broadworks SIP stack.This is the biggest project that I know of built on top of broadworks 3rd party api. We had a product owner who had spent last 10 year with callcenter solutions. So you can say it was a smooth sail. Once again It proves that for any successful software, most important thing is ‘Domain Knowledge’. We emphasized heavily on Domain Driven Design, unit testing , functional automation testing and we didn’t forget that none of these can replace the manual testing. At the end, it all paid off. Except for some minor integration issues at deployment time (which is natural because the solution depends on a number of servers talking to each other, a typical pattern for telephony apps) , we didn’t have a single major bug that is a showstopper! We had to change some labels on customer demand and add some more validation, but thats about it!

So here I am , feeling all good about the year 2009 and praying it would go the same for year 2010! And hoping I will update my blog regularly! Wish me luck!

What I liked most about Erlang as a functional language

Erlang comes with all the built-in concurrency and distributed programming support which will blow you away if you are familiar with distributed systems. However, I will save that talk for some other day. If you see erlang as a pure functional language, it is really powerful! And among all functional languages I’v played with (javascript, php, ruby), one thing I looooooved about erlang is that it is a compiled language! Coming from java background, I become really pissed when program fails for silly syntax error!

I have this feeling that the future languages will become more and more like erlang. With all the multi-core processors, thats the way to go! Only thing I don’t like about it is that the syntax is more like mathematics and less like a language. Again, coming from java background, I might have become too comfortable with more descriptive language. Time to get out of the comfort zone!

How to setup Emacs as Erlang IDE on Windows XP

There are a dozens of links in the web for setting up erlang as an emacs IDE with syntax highlighting, indentation, compilation , debugging support. However, none of them is as straight forward as this one HERE. However, this was written for linux boxes. You can replace the linux relative paths with your windows relative paths. Just keep the following points in mind.

  • Erlang default installation path “C:\Program Files\erl5.6.3” is not supported by Distel (because of the whitespace in ‘Program Files’). You can either install erlang in a different location (C:\erl5.6.3), or use shortcut path location : “C:/progra~1/erl5.6.3”
  • You don’t have to checkout the distel code with svn if you don’t have svn client. You can download it directly from HERE.

Just in case, I am pasting my .emacs file here

;; This is needed for Erlang mode setup
(setq erlang-root-dir "c:/progra~1/")
(setq load-path (cons "C:/progra~1/erl5.6.3/lib/tools-2.6.1/emacs" load-path))
(setq exec-path (cons "c:/progra~1/erl5.6.3/bin" exec-path))
(require 'erlang-start)

;; This is needed for Distel setup
(let ((distel-dir "C:/dev/erlang/distel/elisp"))
  (unless (member distel-dir load-path)
    ;; Add distel-dir to the end of load-path
    (setq load-path (append load-path (list distel-dir)))))

(require 'distel)
(distel-setup)

;; Some Erlang customizations
(add-hook 'erlang-mode-hook
	  (lambda ()
	    ;; when starting an Erlang shell in Emacs, default in the node name
	    (setq inferior-erlang-machine-options '("-sname" "emacs"))
	    ;; add Erlang functions to an imenu menu
	    (imenu-add-to-menubar "imenu")))

;; A number of the erlang-extended-mode key bindings are useful in the shell too
(defconst distel-shell-keys
  '(("\C-\M-i"   erl-complete)
    ("\M-?"      erl-complete)
    ("\M-."      erl-find-source-under-point)
    ("\M-,"      erl-find-source-unwind)
    ("\M-*"      erl-find-source-unwind)
    )
  "Additional keys to bind when in Erlang shell.")

(add-hook 'erlang-shell-mode-hook
	  (lambda ()
	    ;; add some Distel bindings to the Erlang shell
	    (dolist (spec distel-shell-keys)
	      (define-key erlang-shell-mode-map (car spec) (cadr spec)))))

Sun’s Garbage First Collector : do I have to buy it!

Those of you who are involved in real time application development surely have suffered from the unexpected GC pause time. I have spent quite a bit of time trying to tune my Heap generation spaces, playing with new generation / old generation size , GC algorithms etc. I had my eye on the Sun’s Garbage First Collector. Basically it is the replacement of the old CMS (Concurrent mark sweep) collectors and can be extremely useful for large heap low latency applications. I just read this article which suggests sun is going to make this commercial !!! I wonder whether the Oracle acquisition is revealing its true nature! One of the main reason why Java is still my favorite platform (I am falling in love slowly with dynamic languages like Ruby, Erlang, PHP..man! you can do magic with them !l!) is because of its solid and open VM. If sun moves away from its open policies, I wonder if Java will become the legacy language in near future????!!!.