Sunday, February 27, 2011

Parallel Bug #1

This is the first of a collection of bugs from my code (or code in the project I'm involved in). Right now, I'll just collect them. Maybe, at some time in the future, I find time to lay out my ideas about how to deal with this sort of bugs ...

Here is the code for Parallel Bug #1:

    1 using System;
    2 using System.Collections.Generic;
    3 using System.Linq;
    4 using System.Reflection;
    5 using System.Threading.Tasks;
    6 using BF = System.Reflection.BindingFlags;
    7 
    8 #region A Framework For "Extended Enums"
    9 
   10 public abstract class BaseEnum<T, TEnum> 
   11         where T : BaseEnum<T, TEnum> {
   12     public TEnum Enum { get; private set; }
   13     public string Abbr { get; private set; }
   14     public string Name { get; private set; }
   15 
   16     private readonly static IDictionary<TEnum, T> _repository
   17                             = new SortedDictionary<TEnum, T>();
   18 
   19     protected BaseEnum(TEnum @enum, string abbr, string name) {
   20         Enum = @enum;
   21         Abbr = abbr;
   22         Name = name;
   23         _repository.Add(Enum, (T)this);
   24     }
   25 
   26     private static void EnsureInstantiation() {
   27         // Attempt at double checked locking ...
   28         if (_repository.Count == 0) {
   29             lock (_repository) {
   30                 if (_repository.Count == 0) {
   31                     ForceStaticConstructorCall();
   32                 }
   33             }
   34         }
   35     }
   36 
   37     private static void ForceStaticConstructorCall() {
   38         ConstructorInfo constructor = typeof(T)
   39           .GetConstructors(BF.Public | BF.NonPublic | BF.Instance)[0];
   40         object[] pars = constructor.GetParameters()
   41               .Select(p => p.ParameterType.IsValueType
   42                           ? Activator.CreateInstance(p.ParameterType)
   43                           : null)
   44               .ToArray();
   45         try {
   46             T dummy = (T) constructor.Invoke(pars);
   47             // If we get here, we DID add the dummy to the _repository -
   48             // probably because 0 is not in the enum.
   49             _repository.Remove(dummy.Enum);
   50         } catch {
   51             // ignored - we only trigger the static constructor here.
   52         }
   53     }
   54 
   55     public static IEnumerable<T> GetAll() {
   56         EnsureInstantiation();
   57         return _repository.Values;
   58     }
   59 
   60     public static T Get(TEnum e) {
   61         EnsureInstantiation();
   62         return _repository[e];
   63     }
   64 }
   65 
   66 #endregion A Framework For "Extended Enums"
   67 
   68 #region An Application
   69 
   70 public enum Units { km, m, cm, mm, g, kg }
   71 
   72 public class Unit : BaseEnum<Unit, Units> {
   73     public Unit BaseUnit { get; private set; }
   74     public decimal FactorToBaseUnit { get; private set; }
   75 
   76     private Unit(Units @enum, string abbrev, string longName, 
   77                  Unit baseUnit = null, decimal factor = 1.0m)
   78         : base(@enum, abbrev, longName) {
   79         BaseUnit = baseUnit ?? this;
   80         FactorToBaseUnit = factor;
   81     }
   82 
   83     static Unit() {
   84         var m = new Unit(Units.m, "m", "meter");
   85         new Unit(Units.cm, "cm", "centimeter", m, 100);
   86         new Unit(Units.mm, "mm", "millimeter", m, 1000);
   87         new Unit(Units.km, "km", "kilometer", m, 0.001m);
   88         var kg = new Unit(Units.kg, "kg", "kilogram");
   89         new Unit(Units.g, "g", "gram", kg, 1000);
   90     }
   91 }
   92 
   93 class Program {
   94     static void Main() {
   95         var t1 = new Task<Unit>(() => Unit.Get(Units.cm));
   96         var t2 = new Task<Unit>(() => Unit.Get(Units.mm));
   97         t1.Start();
   98         t2.Start();
   99         Console.WriteLine(t1.Result.FactorToBaseUnit / t2.Result.FactorToBaseUnit);
  100     }
  101 }
  102 
  103 #endregion An Application


If you run the program, you see as output 0.1. However, this output is not reliable: The program can throw an exception if one of the tasks runs EnsureInstantiation while the other is in the middle of the static constructor - then, only part of _repository will be filled, and Get might not find a key. The bug is, of course, that in the double checked locking code, Count == 0 is not a sufficient condition to check that the initialization is complete.

Saturday, February 26, 2011

Formatting postings ...

Mhm. The small font in my last posting was obviously too small (but the preview didn't show it like that).
On the other hand, the HTML copying of C# from VS 2010 worked quite well: I use http://copysourceashtml.codeplex.com/, with the VS 2010 installation guide at http://geekswithblogs.net/seanfao/archive/2009/12/06/136793.aspx.
What's not so nice: The long lines in my code are broken around willy-nilly .. I have to learn to "program for blogging", it seems.

We'll see!

-----

Ok - I redid it:
  • Fonts are now all normal size.
  • And I reformatted the C# code - up to 69 columns create no line break!
Have a nice day!

Creating All-Pairs Unit Test Code with PICT and StringTemplate

First of all, what is "All-Pairs Testing", what is "PICT", what is "StringTemplate"?
PICT's output is not code, but a tab-separated text file containing one line for each test case. Using StringTemplate, we can create partial source code for unit tests by adding a few lines of "glue code"; or rather, creating a "glue tool" which I call PICT2Code. Here is its usage together with PICT:

    PICT model.pict | PICT2Code code.template > code.cs

model.pict is a PICT definition of the test variables. Here is a simple example:

P1: 0, 1, 100
P2: null, "SomeValue"
P3: 0.0m, 1.0m, 2.0m


(Full combinatorial testing would create 3*3*2 = 18 test cases for these values. All-pairs testing creates 9).

code.template is a StringTemplate template describing the code. Here is an example:

[Test]
public void Test_$P1_AsIdent$_$P2_AsIdent$_$P3_AsIdent$() {
    new MyClass().Method($P1$, $P2$, $P3$);
    throw new NotImplementedException("Asserts are missing!");
}


This looks like "real code", except for the $...$ segments which mirror the variables defined in model.pict. The segments come in two variants:
  • $P1$ etc. will be replaced with a value from the test model.
  • $P1_AsIdent$ etc. will be replaced with a string that contains only letters and digits.
Here is the beginning of the generated code - it is obviously crude, but might be helpful:


[Test]
public void Test_P1_is_100_P2_is_null_P3_is_0_0m() {
    new MyClass().Method(100, null, 0.0m);
    throw new NotImplementedException("Asserts are missing!");
}
[Test]
public void Test_P1_is_1_P2_is_null_P3_is_2_0m() {
    new MyClass().Method(1, null, 2.0m);
    throw new NotImplementedException("Asserts are missing!");
}
[Test]
public void Test_P1_is_0_P2_is__SomeValue__P3_is_0_0m() {
    new MyClass().Method(0, "SomeValue", 0.0m);
    throw new NotImplementedException("Asserts are missing!");
}
[Test]
...


You get the idea.


Finally, here is the source code of PICT2Code:

    1 using System;
    2 using System.IO;
    3 using System.Text.RegularExpressions;
    4 using Antlr3.ST;
    5 
    6 namespace PICT2Code {
    7     class PICT2CodeMain {
    8         class ErrorListener : IStringTemplateErrorListener {
    9             public void Error(string msg, Exception e) {
   10                 Console.Error.WriteLine(msg + ": " + e.Message);
   11             }
   12             public void Warning(string msg) {
   13                 Console.Error.WriteLine(msg);
   14             }
   15         }
   16 
   17         static void Main(string[] args) {
   18             if (args.Length < 1) {
   19                 Usage("Missing parameters");
   20                 Environment.Exit(1);
   21             }
   22             var codeTemplateFile = args[0];
   23             string codeTemplate;
   24             using (var tr = new StreamReader(codeTemplateFile)) {
   25                 codeTemplate = tr.ReadToEnd();
   26             }
   27 
   28             using (var pictOutput = args.Length > 1
   29                         ? new StreamReader(args[1]) : Console.In) {
   30                 string[] varnames = ReadLine(pictOutput);
   31                 var nonIdentChars = new Regex("[^a-zA-Z0-9_]");
   32                 for (;;) {
   33                     string[] data = ReadLine(pictOutput);
   34                     if (data == null) {
   35                         break;
   36                     }
   37                     var st = new StringTemplate(codeTemplate)
   38                             { ErrorListener = new ErrorListener() };
   39                     for (int i = 0; i < data.Length; i++) {
   40                         st.SetAttribute(varnames[i], data[i]);
   41                         st.SetAttribute(varnames[i] + "_AsIdent",
   42                             varnames[i] + "_is_"
   43                             + nonIdentChars.Replace(data[i], "_"));
   44                     }
   45                     Console.WriteLine(st.ToString());
   46                 }
   47             }
   48         }
   49 
   50         private static void Usage(string msg) {
   51             Console.Error.WriteLine(msg);
   52             Console.Error.WriteLine();
   53             Console.Error.WriteLine(@"
   54 Usage: PICT2Code <template file> [<pict output file>]
   55 
   56 Typical calls are
   57     PICT model.pict | PICT2Code code.template > code.cs
   58 and
   59     PICT model.pict /e:tests.old > tests.new
   60     PICT2Code code.template tests.new > code.cs
   61 ");
   62         }
   63 
   64         private static string[] ReadLine(TextReader pictOutput) {
   65             string line = pictOutput.ReadLine();
   66             return line == null ? null : line.Split('\t');
   67         }
   68     }
   69 }
   70 


Happy testing!

Friday, February 25, 2011

Software Engineering Musings

So I've started another blog! On software engineering thoughts (the other one is at www.harmonic-musings.blogspot.com; it has a somewhat different focus). I am a professional software engineer - a software architect, some call it.

Right now, I live in Munich, Germany, after a long time in Vienna, Austria, and a few years in San Jose, California. I did the full round of the "big OO languages" in my projects: C++ at the end of the '80s, some Smalltalk and then of course Java in the '90s and up to 2005, C# since then. File systems, DB2 and SQL Server are the persistence layers I learned to live with; and TopLink, my home-grown and then Hibernate and NHibernate the OR-Mappers to access them.

Along the way, I probably had every problem one can have and made every mistake one can make when designing and writing application frameworks for others. Some of them (the problems, the mistakes, and maybe also the frameworks) have accompanied me for a long time - so that I feel it makes sense to share them here. If - and when - I find answers or solutions to some of them, I might also share them.

Let's see!