среда, 18 марта 2015 г.

Запуск на эмуляторе

  1. Качаем Android SDK если не устанавливали  вместе с Rad Studio
  2. Запускаем AVD Manager

3. Создаём устройство

Необходимо указать использование GPU и не включать SnapShot. У меня иначе виснет, и тормозит. Может кривые руки.

Но, при таких настройках эмулятор притормаживает, но работает.

После нажатия ОК.

4. Запускаем эмулятор

Нажимаем Launch

Видим окно загрузки

5. Пока загружается эмулятор, у меня загрузка происходит примерно минут от 3 до 7. (AMD 8x3,5 Mhz. 8 gb DDR3.) Запускаем Rad Studio

После загрузки эмулятора, видим такое окно:

7. Выбираем платформу Android и Target в нашем случае MyDevice7

8. Делаем Build
Памяти съел около 800 Mb. Build длился около 6 минут.
Build.gif



9. Запускаем и ловим

First chance exception at $B6F34CE2. Exception class Illegal instruction (4). Process MindStream.apk (1433)

Нажимаем ок и ждем.

Результат:
Run.gif

To be honest… Delphi for Android "evokes no enthusiasm "...

Original in Russian: http://programmingmindstream.blogspot.com/2015/03/delphi-android.html

To be honest… Delphi for Android "evokes no enthusiasm "...
To be honest... Delphi for Android “evokes no enthusiasm”... Yes, indeed...

As for our project, this eats up the memory of 1,5 Gb under compilation and fails with Internal Error.

[DCC Fatal Error] msShape.pas(387): F2084 Internal Error: GPFE06D7363-761F2F71-0

GPF is, OBVIOUSLY, General Protection Fault.

12k lines of the code and 1.5 Gb of the MEMORY. Isn’t it just too much?

Bunglers.

Not to mention the fact that compilation slows down by 10-20 times compared to Windows.

Sure, we could post a bug in QC, but I really doubt that anybody anytime soon would at least glance at it.

On the contrary, the project is successfully built under Win-64. It can also be run from the command line, but under the debugger we have - Unable to create process.

Indeed so...

"Hello world" – is as far as we can go for now...

Actually, “single code base” seems to be a “marketeers’ idea”.

Note, that the project has been written FROM SCRATCH for STANDARD CONSTRUCTS.

The source code is here - https://bitbucket.org/ingword/mindstream/src/0745fccded2b070c03850e74c9d4644a680f1da1/?at=B-Samsung-Try

I can not get away from the strong feeling that I’ve been “deceived and cheated”.

I waved goodbye to JSON, and had all built successfully. I’ve never liked the word “JSON”. Any time I’ve dealt with it, it only caused a pain in the rear.

Finally, I have given a “bumper start” to the application.

Here is the photo:


Naturally, I have problems with lines as well as with clicks motility.

Briefly. Again about factories

Original in Russian: http://programmingmindstream.blogspot.ru/2014/09/blog-post_3.html

In a way, based on:

Briefly. About factories and http://programmingmindstream.blogspot.ru/2014/08/istorage-tdd.html

What do I want to talk about?

We have our own implementation of IStorage (and, therefore,  IStream).

It is good. In a sense. It is good at least because it works stable for already 15 years.

But there are “some problems”.

I examine these problems scrupulously now.

I do not speak about the “problems” of complex implementation in heterogeneous network environment.

There are “local problems”, too.

For example, the fact that everything is built there on "binary serialisation"  .

I.e. something like this:

type
 TStoreHeader = record
  rNextPosition : Int64;
  rRealSize : Int64;
  ...
 end;//TStoreHeader
 
...
 
procedure SomeReadCode;
var
 l_H : TStoreHeader;
begin
 ...
 Stream.Read(l_H, SizeOf(l_H);
 ...
end;
 
...
 
procedure SomeWriteCode;
var
 l_H : TStoreHeader;
begin
 ...
 Stream.Write(l_H, SizeOf(l_H);
 ...
end;

You can look at the “real code” here.

What’s the problem? The problem is the format of TStoreHeader cannot be changed “for no reason” – everything will “get displaced”.

What should we do?

To begin with, we do something like this:

type
 TStoreHeaderRec = record
  rNextPosition : Int64;
  rRealSize : Int64;
  ...
 end;//TStoreHeaderRec
 
 TStoreHeader = class
  private
   Data : TStoreHeaderRec;
  public
   procedure Load(aStream: TStream);
   procedure Save(aStream: TStream);
 end;//TStoreHeader
 
...
 
procedure TStoreHeader.Load(aStream: TStream);
begin
 aStream.ReadBuffer(Data, SizeOf(Data);
end;
 
procedure TStoreHeader.Save(aStream: TStream);
begin
 aStream.WriteBuffer(Data, SizeOf(Data);
end;
 
...
 
procedure SomeReadCode;
var
 l_H : TStoreHeader;
begin
 ...
 l_H := TStoreHeader.Create;
 ...
 l_H.Load(Stream);
 ...
end;
 
...
 
procedure SomeWriteCode;
var
 l_H : TStoreHeader;
begin
 ...
 l_H := TStoreHeader.Create;
 ...
 l_H.Save(Stream);
 ...
end;

What’s next?

Something like this:

type
 TStoreHeaderAbstract = class
  public
   procedure Load(aStream: TStream); virtual; abstract;
   procedure Save(aStream: TStream); virtual; abstract;
 end;// TStoreHeaderAbstract
 
...
 
 TStoreHeaderRec = record
  rNextPosition : Int64;
  rRealSize : Int64;
  ...
 end;//TStoreHeaderRec
 
 TStoreHeader = class(TStoreHeaderAbstract)
  private
   Data : TStoreHeaderRec;
  public
   procedure Load(aStream: TStream); override;
   procedure Save(aStream: TStream); override;
 end;//TStoreHeader
 
 TStoreHeaderFactory = class
  public
   class function Make: TStoreHeaderAbstract;
 end;//TStoreHeaderFactory
 
...
 
class function TStoreHeaderFactory.Make: TStoreHeaderAbstract;
begin
 Result := TStoreHeader.Create;
end;
 
procedure TStoreHeader.Load(aStream: TStream);
begin
 aStream.ReadBuffer(Data, SizeOf(Data);
end;
 
procedure TStoreHeader.Save(aStream: TStream);
begin
 aStream.WriteBuffer(Data, SizeOf(Data);
end;
 
...
 
procedure SomeReadCode;
var
 l_H : TStoreHeaderAbstract;
begin
 ...
 l_H := TStoreHeaderFactory.Make;
 ...
 l_H.Load(Stream);
 ...
end;
 
...
 
procedure SomeWriteCode;
var
 l_H : TStoreHeaderAbstract;
begin
 ...
 l_H := TStoreHeaderFactory.Make;
 ...
 l_H.Save(Stream);
 ...
end;

What have we done here?

We’ve introduced a base abstract class - TStoreHeaderAbstract and a factory - TStoreHeaderFactory.

How can we change data format?

In this way:

type
 TStoreHeaderAbstract = class
  public
   procedure Load(aStream: TStream); virtual; abstract;
   procedure Save(aStream: TStream); virtual; abstract;
 end;// TStoreHeaderAbstract
 
...
 
 TStoreHeaderRec = record
  rNextPosition : Int64;
  rRealSize : Int64;
  ...
 end;//TStoreHeaderRec
 
 TStoreHeader = class(TStoreHeaderAbstract)
  private
   Data : TStoreHeaderRec;
  public
   procedure Load(aStream: TStream); override;
   procedure Save(aStream: TStream); override;
 end;//TStoreHeader
 
 TStoreHeaderRecNew = record
  rNextPosition : Int64;
  rRealSize : Int64;
  rSomeOtherData : SomeOtherType;
  ...
 end;//TStoreHeaderRecNew
 
 TStoreHeaderNew = class(TStoreHeaderAbstract)
  private
   Data : TStoreHeaderRecNew;
  public
   procedure Load(aStream: TStream); override;
   procedure Save(aStream: TStream); override;
 end;//TStoreHeaderNew
 
 TStoreHeaderFactory = class
  public
   class function Make(aVersion : TGUID): TStoreHeaderAbstract;
 end;//TStoreHeaderFactory
 
...
 
class function TStoreHeaderFactory.Make(aVersion : TGUID): TStoreHeaderAbstract;
begin
 if EqualGUID(aVersion, OldFormatGUID) then
  Result := TStoreHeader.Create
 else
 if EqualGUID(aVersion, NewFormatGUID) then
  Result := TStoreHeaderNew.Create
 else
  Assert(false, 'Incorrect header');
end;
 
procedure TStoreHeader.Load(aStream: TStream);
begin
 aStream.ReadBuffer(Data, SizeOf(Data);
end;
 
procedure TStoreHeader.Save(aStream: TStream);
begin
 aStream.WriteBuffer(Data, SizeOf(Data);
end;
 
...
 
procedure TStoreHeaderNew.Load(aStream: TStream);
begin
 aStream.ReadBuffer(Data, SizeOf(Data);
end;
 
procedure TStoreHeaderNew.Save(aStream: TStream);
begin
 aStream.WriteBuffer(Data, SizeOf(Data);
end;
 
...
 
procedure SomeReadCode;
var
 l_H : TStoreHeaderAbstract;
begin
 ...
 l_H := TStoreHeaderFactory.Make(GetVersionGUID);
 ...
 l_H.Load(Stream);
 ...
end;
 
...
 
procedure SomeWriteCode;
var
 l_H : TStoreHeaderAbstract;
begin
 ...
 l_H := TStoreHeaderFactory.Make(GetVersionGUID);
 ...
 l_H.Save(Stream);
 ...
end;

Moreover, we can also write in this way:

...
procedure TStoreHeaderNew.Load(aStream: TStream);
begin
 aStream.ReadBuffer(Data.rRealSize, SizeOf(Data.rRealSize);
 aStream.ReadBuffer(Data.rNextPosition, SizeOf(Data.rNextPosition);
 aStream.ReadBuffer(Data.rSomeOtherData, SizeOf(Data.rSomeOtherData);
end;
 
procedure TStoreHeaderNew.Save(aStream: TStream);
begin
 aStream.WriteBuffer(Data.rRealSize, SizeOf(Data.rRealSize);
 aStream.WriteBuffer(Data.rNextPosition, SizeOf(Data.rNextPosition);
 aStream.WriteBuffer(Data.rSomeOtherData, SizeOf(Data.rSomeOtherData);
end;

What have we done here?

In the first place, we’ve divided “binary serialization” of a record into a few “binary serializations” of separate fields.

In the second place, we’ve reversed some of the fields in order to demonstrate “the heart of the approach”.

Lots of questions are left beyond the scope.

For example – “where do we get GetVersionGUID?”

Or – “what should we do if writing of version is not supported initially”?

These are important questions. But they “do not fit in the framework” of the post. Generally speaking, these are important, but more “technical questions”. If you are interested I will analyze them in detail.

For now I’ll leave them “beyond the scope”.

What is the result?

In sum, in my opinion, it has been shown that factories  are a weighty supplement to encapsulation  and polymorphism.

First we’ve used polymorphism - by introducing the type TStoreHeaderAbstract.
Then we’ve used encapsulation – by dividing TStoreHeader.Data and TStoreHeaderNew.Data.

Through polymorphism and encapsulation we’ve, in a way, avoided the “binary serialization”.

Why?
Because the next step could be the following:

...
procedure TStoreHeaderNew.Load(aStream: TStream);
begin
 aStream.ReadBuffer(Data.rRealSize, SizeOf(Data.rRealSize);
 aStream.ReadBuffer(Data.rNextPosition, SizeOf(Data.rNextPosition);
 Data.rSomeOtherData.Load(aStream);
end;
 
procedure TStoreHeaderNew.Save(aStream: TStream);
begin
 aStream.WriteBuffer(Data.rRealSize, SizeOf(Data.rRealSize);
 aStream.WriteBuffer(Data.rNextPosition, SizeOf(Data.rNextPosition);
 Data.rSomeOtherData.Save(aStream);
end;

- i.e. here we no more write/read “binarily”, but as it is written in SomeOtherDataType.Load/SomeOtherDataType.Save.

So.

What I wanted to show?

I’ll repeat.

I wanted to show that factories are a weighty supplement to encapsulation and polymorphism.

(Let’s say, factories are “twice the polymorphism”. That is because polymorphism “starts to have effect” even before object instance is created. Then, factory polymorphism works. Should I write about polymorphic factories?)

You are to judge how much I succeeded in achieving my goal.

I guess I did not “reinvented the wheel”, but I hope I’ve written something of use.




Briefly. Some more "reasoning about RAII"

Original in Russian: http://programmingmindstream.blogspot.ru/2014/09/raii.html

Based on - RAII

I’d like to write about “integral objects”.

I mean, the objects that create other objects internally.

Usually it is done in this way:

type
 TSomeClass1 = class
  public
   constructor Create(aSomeData1 : TSomeType1);
 end;//TSomeClass1
 
 TSomeClass = class
  private
   f_SomeClass1 : TSomeClass1;
   f_SomeClass2 : TSomeClass2;
  public
   constructor Create(aSomeData1 : TSomeType1; aSomeData2: TSomeType2);
   destructor Destroy; override;
 end;//TSomeClass
 
...
 
constructor TSomeClass1.Create(aSomeData1 : TSomeType1);
begin
 Assert(IsValid(aSomeData1));
 inherited Create;
 ...
end;
 
...
 
constructor TSomeClass.Create(aSomeData1 : TSomeType1; aSomeData2: TSomeType2);
begin
 inherited Create;
 f_SomeClass1 := TSomeClass1.Create(aSomeData1);
 SomeInitCode;
 f_SomeClass2 := TSomeClass2.Create(aSomeData2);
end;
 
destructor TSomeClass.Destroy;
begin
 FreeAndNil(f_SomeClass2);
 SomeDoneCode;
 FreeAndNil(f_SomeClass1);
 inherited;
end;

But sometimes it can be done like this:

type
 TSomeClass = class
  private
   f_SomeClass1 : TSomeClass1;
   f_SomeClass2 : TSomeClass2;
  protected
   constructor Make(aSomeClass1 : TSomeClass1; aSomeClass2: TSomeClass2);
  public
   class function Create(aSomeData1 : TSomeType1; aSomeData2: TSomeType2): TSomeClass;
   destructor Destroy; override;
 end;//TSomeClass
 
constructor TSomeClass.Make(aSomeClass1 : TSomeClass1; aSomeClass2: TSomeClass2);
begin
 inherited Create;
 f_SomeClass1 := aSomeClass1;
 SomeInitCode;
 f_SomeClass2 := aSomeClass2;
end;
 
class function TSomeClass.Create(aSomeData1 : TSomeType1; aSomeData2: TSomeType2): TSomeClass;
var
 l_SomeClass1: TSomeClass1;
 l_SomeClass2: TSomeClass2;
begin
 Assert(IsValid(aSomeData1));
 Assert(IsValid(aSomeData2));
 l_SomeClass1 := TSomeClass1.Create(aSomeData1);
 l_SomeClass2 := TSomeClass2.Create(aSomeData2);
 Result := Make(l_SomeClass1, l_SomeClass2);
end;
 
destructor TSomeClass.Destroy;
begin
 FreeAndNil(f_SomeClass2);
 SomeDoneCode;
 FreeAndNil(f_SomeClass1);
 inherited;
end;

What is “delicious”?

The instance of TSomeClass will not be created until the instances of TSomeClass1 and TSomeClass2 are created.

It means, the destructor will not be called.

Therefore, SomeInitCode won’t be called either.

Hence, there will be no problems with destructing of a partially created object.

There are questions about SomeDoneCode. It will be called (we look into documentation).

But!

If it depends only from the objects initialized above, there will be no problems either.

Besides, it will be called only if the lines:

...
 l_SomeClass1 := TSomeClass1.Create(aSomeData1);
 l_SomeClass2 := TSomeClass2.Create(aSomeData2);
...

- are executed.

And the lines:

...
 inherited Create;
 f_SomeClass1 := aSomeClass1;
...

- do not pass for some reason.

But in this area “the probability tends to zero”.

We can also write in this way:

type
 TSomeClass = class
  private
   f_SomeClass1 : TSomeClass1;
   f_SomeClass2 : TSomeClass2;
  protected
   constructor Make(aSomeClass1 : TSomeClass1; aSomeClass2: TSomeClass2);
  public
   class function Create(aSomeData1 : TSomeType1; aSomeData2: TSomeType2): TSomeClass;
   destructor Destroy; override;
 end;//TSomeClass
 
constructor TSomeClass.Make(aSomeClass1 : TSomeClass1; aSomeClass2: TSomeClass2);
begin
 inherited Create;
 f_SomeClass1 := aSomeClass1;
 SomeInitCode;
 f_SomeClass2 := aSomeClass2;
end;
 
class function TSomeClass.Create(aSomeData1 : TSomeType1; aSomeData2: TSomeType2): TSomeClass;
var
 l_SomeClass1: TSomeClass1;
 l_SomeClass2: TSomeClass2;
begin
 l_SomeClass1 := TSomeClass1.Create(aSomeData1);
 l_SomeClass2 := TSomeClass2.Create(aSomeData2);
 Result := Make(l_SomeClass1, l_SomeClass2);
end;
 
destructor TSomeClass.Destroy;
begin
 FreeAndNil(f_SomeClass2);
 if (f_SomeClass1 <> nil) then
 // - we check if f_SomeClass1 is initialized
 // Why do we need to check? The answer is – “otherwise, why is this SomeDoneCode exactly HERE?
  SomeDoneCode;
 FreeAndNil(f_SomeClass1);
 inherited;
end;

Or even in this way (for paranoiacs like me):

type
 TSomeClass = class
  private
   f_SomeClass1 : TSomeClass1;
   f_SomeClass2 : TSomeClass2;
  protected
   constructor Make(aSomeClass1 : TSomeClass1; aSomeClass2: TSomeClass2);
  public
   class function Create(aSomeData1 : TSomeType1; aSomeData2: TSomeType2): TSomeClass;
   destructor Destroy; override;
 end;//TSomeClass
 
constructor TSomeClass.Make(aSomeClass1 : TSomeClass1; aSomeClass2: TSomeClass2);
begin
 inherited Create;
 f_SomeClass1 := aSomeClass1;
 SomeInitCode(f_SomeClass1);
 f_SomeClass2 := aSomeClass2;
end;
 
class function TSomeClass.Create(aSomeData1 : TSomeType1; aSomeData2: TSomeType2): TSomeClass;
var
 l_SomeClass1: TSomeClass1;
 l_SomeClass2: TSomeClass2;
begin
 l_SomeClass1 := TSomeClass1.Create(aSomeData1);
 l_SomeClass2 := TSomeClass2.Create(aSomeData2);
 Result := Make(l_SomeClass1, l_SomeClass2);
end;
 
destructor TSomeClass.Destroy;
begin
 FreeAndNil(f_SomeClass2);
 if (f_SomeClass1 <> nil) then
 // - we check if f_SomeClass1 is initialized
 // Why do we need to check? The answer is – “otherwise, why is this SomeDoneCode exactly HERE?
  SomeDoneCode(f_SomeClass1);
 FreeAndNil(f_SomeClass1);
 inherited;
end;

- probably, it is more clear this way?

Finally, TSomeClass1.Create and TSomeClass2.Create can be created “inductively” if they are also integral.

We should not forget that if constructor throws an exception, the destructor is definitely called (it is also written in documentation), even on a “partially initialized object”.

I’ll repeat once more:

If constructor throws an exception, the destructor is called definitely - even on a “partially initialized object”.

It should be taken into account.

For this reason I had a post - http://programmingmindstream.blogspot.ru/2014/09/blog-post_9.html

What do I lead to?

The “smaller” our objects are, the “smaller” the probability to have destructor call on “partially initialized object” is.

I’ll repeat:

The “smaller” our objects are, the “smaller” the probability to have destructor call on “partially initialized object” is.

It means working less in the constructor instead of factory provides more “peace” for us, as well as decreases the probability of getting AccessViolation or/and unreleased resources.

I’ll be glad if my thoughts will be helpful for anybody.

I’ve used them myself. Several times. But, that is not a measure.

If you’re actually interested, I could try to follow using a real example.

P.S. What we got using this approach is that, on the one hand, we kind of encapsulate “inner logic” in class and, on the other hand, avoid creating a “partially initialized object instance”.

In general, I personally like factories, mixins and Assert’s for a while now. It seems to be serious and long-lasting.


Objective-C and Delphi

Original in Russian: http://18delphi.blogspot.ru/2013/03/objective-c-delphi.html

More alike than different.

In fact, it was not a trouble to partly shift from developing in Delphi to Objective-C.

Unfortunately, too harshly. It looks like Delphi returns to life.

Later on I’ll continue writing this post and explaining the likeness of these two languages.
Meanwhile the following points:
1. Single inheritance.
2. Interfaces (protocols).
3. Metaclasses. class and CLASS.
3. Common for all objects base parent class - TObject and NSObject.
4. Reference counting.
5. TList and NSArray. The first does not have reference counting, though.
6. Controls and graphics contexts. These have LOTS in common.
7. Event's ad selector's.
8. Virtual classes methods.
9. Creating of objects (descendants of TObject and NSObject) only in a heap.
10. Means to override objects allocation (NewInstance and alloc).
11. Categories in Objective-C and helper's in Delphi.

Sure, in some aspects Objective-C “beats” Delphi, and vise versa. I’ll try to write about it, too.

Meanwhile, NSOperation is very cool for mobile devices. I don’t know yet if there is an analogue in Delphi XE. If there is not any, Delphi has a GREAT disadvantage.
I’d also mention NSDictionary, NSArray and native serialization.

Again, I am very attached to the concept of objects reference counting implemented in Objective-C. I have “devised” a concept of this sort, although I did not hit upon autoreleasepool (well, I didn’t need it).

If we develop not in Objective-C (.m), but in Objective-C++ (.mm), we “go over to the enemy” :-) Nevertheless, we get access to the “great and powerful” STL. Thus, we do not have to recreate wheels. Especially “tasteful” is the fact that Objective-C classes and C++ classes mix TRANSPARENTLY. Of course, this is done without cross-inheritance, which is NOT NEEDED. Aggregation is more than enough.

I’ll not even touch upon C++ 11 standard and its “goodies” – particularly with regard to metaprogramming. The mere auto and lambdas are worth something. (By the way, there are lambdas (block) in Objective-C, as well as in Delphi XE, as far as I remember, but I, to be honest, have not yet tried it and have nothing to tell).

I will not also mention patterns with a variable number of parameters (that’s the kind!).

Generally speaking – “a stitch in time saves nine” and each tool is good for something particular.

Delphi is more “dear” to me personally since I have been developing 18 years in it. Actually, I started with developing in Turbo Pascal 3.0 (probably, not everyone remembers).

But now I am not afraid of Objective-C. Quite the contrary – I have found it is much familiar to me.

I’ll just say a commonplace: “if you wish to find the difference – you find the difference, and if you wish to find the likeness – you certainly find the likeness”.

Try it. May be you will like it.

... to be continued ...



Depression, or Falsity of hResult and other ErrorCode’s

Original in Russian: http://programmingmindstream.blogspot.ru/2014/09/blog-post_23.html

So, “third time unlucky”…

More precisely, for the third time the launch of internal product failed.

My fault.

Although it had “all the trimmings”, tests, factories and so no and so forth.

Tests of all sorts – pass.

The real soft – does not work in the real conditions.

READ_ERROR and then WRITE_ERROR. Under distributed access.

Generally, I will not write to blog until I puzzle it out, reconsider it and give “a treatment plan”.

For there is no point in it.

For “theory without practice is useless”.

There’s no point in writing about “cows in vacuum” when “your own cows do not work”.

For now – we’ve created a “load test”.

I left it working for a night.

I would look at it the next morning.

It is possible we’ll write “another load test”.

For quite the time I haven’t seen such epic fails.

I may tell you one thing “not about myself” – hResult and other “ErrorCode’s” are in a way “not a great gimmick”. Of course, it depends on how you use it… I have not always used it properly…

I had:

 SetFilePos(hFile, aPos);
 FileWrite(hFile, @SomeValue, SizeOf(SomeValue));
 SetFilePos(hFile, aPos);
 FileRead(hFile, @SomeOtherValue, SizeOf(SomeOtherValue));
 Assert(SomeValue = SomeOtherValue);

I have:

 SetFilePos(hFile, aPos);
 FileWrite(hFile, @SomeValue, SizeOf(SomeValue));
 SetFilePos(hFile, aPos);
 SysCheck(FileRead(hFile, @SomeOtherValue, SizeOf(SomeOtherValue)));
 // - here it began to fall “sometimes”, despite the fact that SomeOtherValue = SomeValue
 Assert(SomeValue = SomeOtherValue);

- it is a surprise that without SysCheck – the check passed.

It means, the error has been returned but the CHECK passed.

It works in this way:

 SetFilePos(hFile, aPos);
 FileWrite(hFile, @SomeValue, SizeOf(SomeValue));
 SetFilePos(hFile, aPos);
 try
  SysCheck(FileRead(hFile, @SomeOtherValue, SizeOf(SomeOtherValue)));
  // - here it began to fall “sometimes”, despite the fact that SomeOtherValue = SomeValue
 finally
  Assert(SomeValue = SomeOtherValue);
  // - here it does NOT FALL, although it “falls” more, on SysCheck
 end;

It means, “sometimes what’s intended to be read is read”, but with an error.

WITHOUT checking for errors everything worked but “malfunctioned”, with the “check for errors” it began to “fall more often”.

It was so in terms of distributed heterogeneous environment, using “dead” or “half-dead” computers, different Windows versions up to the “prehistoric” ones.

Why? Yet not clear.

Anyway, I blame my own “butter fingers” instead of “Microsoft guys”.

For it is easy to “point fingers at others”.

Simple, but not constructive.

And, by the way, if we write:

SetFilePos(hFile, aPos);
FileWrite(hFile, @SomeValue, SizeOf(SomeValue));
l_TryCount := 0;
while (l_TryCount < 100) do
begin
 Inc(l_TryCount);
 SetFilePos(hFile, aPos);
 try
  SysCheck(FileRead(hFile, @SomeOtherValue, SizeOf(SomeOtherValue)));
  // - here it began to fall “sometimes”, despite the fact that SomeOtherValue = SomeValue
 except
  if (l_TryCount < 100) then
   continue
  else
   raise;
 end;//try..except
 break;
end;
 Assert(SomeValue = SomeOtherValue);

Again, it “falls less often”.

The reason “for thinking”.

Unfortunately, it does not repeat on “synthetic tests”.

In order to top it off, LockRegion/UnlockRegion also engage in it.

Naturally, they are “correctly established” and “properly” framed SysCheck etc.

BUT it looks like their presence is the issue.

Without them, it “seems to work” but with “concurrent access” it works bad, which is obvious.

We’ll move to a new testing level.

P.S. The whole code given above is, of course, “pseudocode” used to illustrate problems. More likely, “comas” are put there incorrectly. SysCheck at FileWrite is also skipped on purpose. Believe me, we have it.

In addition, WrittenSize and ReadSize are checked there.

However, these details are also skipped – on purpose.

P.P.S. The code without SysCheck and OleCheck works for already 15 years, but malfunctions (now and then, I mean what I say, we get incorrect data; it’s unpleasant, but “possible to live”). That is actually the reason why I went in for puzzling it out and writing SysCheck and OleCheck.

As a result, I have got “the whole nine yards”.

Nine yards of what? I don’t know yet.

Once again, it does not repeat at all client stations. May be due to the “butter fingers”.

In short – “do not forget about errors codes”, though “processing” of them is “not always clear”.

When I establish the guilt of my “butter fingers” I will surely write.

P.P.P.S. I’ve also added logging of problem operations. And… And… I’ve got a “Schrödinger's cat”. Logging began to influence “business logic”, at least in terms of “timing delays”, which is obvious.

Again – a “separate issue”.

P.P.P.P.S. One more thing. The code given above is for one client. One writes, the other one reads is not the case. I understand using it for different clients, but not for one client. I hope, I will soon understand.

P.P.P.P.S. Another more thing.

I have already checked the version of this kind:

function DoRead(...): LongBool;
begin
 FileRead(hFile ...);
 // - we “forgot” to return the result
end;
...
SysCheck(DoRead(...));
// - we check for “garbage”

“At a rough guess” there are no “uninitialized variables”.

P.P.P.P.P.S. By the way, my tests are still working… No errors :-( It “inspires sadness”. I’ll see how it ends in the morning.

P.P.P.P.P.S. As it turned out, two things ensure problems:

1. Access through the UNC paths, i.e. paths like - \\server\resource\path\filename.
2. Using of LockFile as a must.

P.P.P.P.P.P.S. Yesterday tests worked without errors. Data in the 3 Gbs area has been processed.

Today I launched tests on two computers. Tomorrow I’ll see the results.

Then I will launch on 3, 4, 5 and so on.

P.P.P.P.P.P.P.S. Today I also found two “bottlenecks” - AllocNewFATAlement and AllocNewCluster. In their turn, they lead to LockFile. We lock the header that stores the information about storage structure. All users “beat against these blockings” while writing.

I already know how to solve it.

We should pro-actively allocate a number (five, ten, twenty) of FATElement and Cluster at once instead of one. As one piece. Given that a file usually contains more than one or two or even ten clusters, it is effective. We should also keep a list of free ones locally by a client. Then, when a client closes, they should be put back to the list of free, “not used” ones. This is done so that other client could use it afterwards.

Sure, there is a possibility that we lose items if a client provokes the fail.

But we get to the “bottleneck” less often, because we can write:

if AllocatedFatElements.Empty then
begin
 // - there is inter-PROCESS “bottleneck”
 Lock;
 try
  Result := AllocNewFatElement;
  for l_Index := 0 to 10 do
   AllocatedFatElements.Add(AllocNewFatElement);
 finally
  Unlock;
 end//try..finally
end
else
 // - here is ONLY inter-THREADED “bottleneck” (because AllocatedFatElements is – naturally – protected by a number of threads)
 Result := AllocatedFatElements.GetLastAndDeleteIt;

Instead of:

Lock;
// - there is ALWAYS a multi-PROCESS “bottleneck”
try
 Result := AllocNewFatElement;
finally
 Unlock;
end;//try..finally

It is the same for clusters.

Even if the elements will  “hang”, t will not get worse and the storage will not be broken. It will have “holes”.

Taking into account the fact that at night the “night Update” takes place (if it is possible), the storage is repacked - in any event. It means the “holes” will disappear - in any case – by morning.

As a result, we’ll have a repacked persistent part with no holes and the “empty” variable part.

During the day clients will write their documents’ versions into the variable part.

The process repeats the next night.

This is, of course, in case there are no working users connected to the base.

A special post is - here. http://programmingmindstream.blogspot.ru/2014/09/blog-post_25.html

Note that this is “all about internal products”. I won’t tell about third-party products.

For those who’ve read to the end, the task looks as follows:


“Deficit” :-(

How to test “untestable” applications

Original in Russian: http://18delphi.blogspot.ru/2014/05/blog-post.html

Let’s say, this is a summary on:
How to test “untestable” applications. Or how to make applications testable.

Let's say – BASIС principles I take from the approach of Sergey Teplyakov who used to write that application testability is an indicator (a litmus paper) of application architecture being “GOOD”.

Once more – the architecture is “good” if it is well tested.

If it is DIFFICULT to write test for a “class”, then why may we think that our colleagues can EASILY use this class?

Teplyakov wrote about it here - Ideal architecture http://sergeyteplyakov.blogspot.ru/2011/11/blog-post_23.html (I actually recommend ALL his articles).

This theme was argued by Roman Yankovsky here - Testable architecture http://roman.yankovsky.me/?p=1541

I’ve written about how I myself found out that testing is needed and that “a good architecture is one that is well tested” and how after that I “began to sleep peacefully” here - http://18delphi.blogspot.ru/2013/03/blog-post.html

However, for long time I’ve been occupied by a thought. It is great if we thought of testing and the “good architecture” from the beginning and applied various approaches (TDD, in particular). But what if we have a GREAT amount of code written with not a slightest thought of testing?

What should we do?

How can we make our application “testable”?

Also, how to “set right” its architecture?

Since then I tried to write many “articles”.

The “prehistorical”:

http://18delphi.blogspot.com/2015/03/gui-testing-13-gui-testing-in-spoken.html

-- there I tried to tell about GUI-testing “as I see it”. I developed testing myself for almost five years.

I also told that GUI-testing is used because of POVERTY, i.e. “BAD ARCHITECTURE”.

It turned out that “people do not understand”.

Then, I made one more attempt:

http://programmingmindstream.blogspot.ru/2014/02/blog-post_4473.html

- where I tried to tell once MORE how to test applications that INITIALLY were not intended for TESTING.

AGAIN, it turned out that “people do not understand”.

At that moment, Vsevolod Leonov showed up and “gave a dare”.

Right here:
http://programmingmindstream.blogspot.ru/2014/02/anemicdomainmodel.html?showComment=1392717297690#c4055365633171954826

I owe SPECIAL THANKS to him for it!

And so a “modern” series “Testing of calculator” came to light:
http://programmingmindstream.blogspot.ru/2014/05/61.html

All seven articles written for now are listed in this reference.

These articles “at first sight” seem to be “banal”, but, as for me, they rise MANY principal questions concerning developing, testing and writing requirements specification.

This series is an “open project”. My colleague from Ukraine and I will develop it. We're planning to write numerous articles and are currently working on it.

In particular, we have a plan - http://programmingmindstream.blogspot.ru/2014/03/blog-post_5.html

Although, our plan was got ahead by life and we have FAR GREATER ideas (I can share with anyone who’s interested).

Besides, we're GLAD to welcome “constructive critics” and CO-AUTHORS.

This is, actually, all “I know about testing” so far.

P.S. We have repository where we’re currently developing - https://bitbucket.org/ingword/lulinproject/src/9674200a1892ab5e2682f740632c9513b3cf9e1e/DummyCalculator/?at=Release

P.P.S. The next part about using “Tests with etalons” has appeared - http://18delphi.blogspot.com/2015/03/testing-of-calculator-61-testing-using.html

Containers 12. DUnit patterns and tests

Original in Russian: http://18delphi.blogspot.ru/2013/03/dunit_9770.html

About containers. Table of contents

The previous series was here:
http://18delphi.blogspot.com/2015/03/a-little-about-using-dunit.html

We’ll develop the idea of testing of TIntStack and TStringStack. But we’ll do it with ONE mixin by removing duplicates of the following kind:

procedure TIntStackTest.DoIt;
const
 cEtalons : array [0..3] of integer = (10, 20, 3, 5);
var
 l_S : TIntStack;
 l_I : Integer;
begin
 l_S := TIntStack.Create;
 try
  for l_I := Low(cEtalons) to High(cEtalons) do
   l_S.Push(cEtalons[l_I]);
  for l_I := High(cEtalons) downto Low(cEtalons) do
   Check(l_S.Pop = cEtalons[l_I]);
 finally
  FreeAndNil(l_S);
 end;//try..finally
end;//TIntStackTest.DoIt

and:

procedure TStringStackTest.DoIt;
const
 cEtalons : array [0..5] of String = ('The ', 'cat ', 'sat ', 'on ', 'the ','mat'); .

var
 l_S : TStringStack;
 l_I : Integer;
begin
 l_S := TStringStack.Create;
 try
  for l_I := Low(cEtalons) to High(cEtalons) do
   l_S.Push(cEtalons[l_I]);
  for l_I := High(cEtalons) downto Low(cEtalons) do
   Check(l_S.Pop = cEtalons[l_I]);
 finally
  FreeAndNil(l_S);
 end;//try..finally
end;//TStringStackTest.DoIt

We draw the diagram as follows:


We get the following code:
SandBox.dpr:

program SandBoxTest;
 
uses
  TestFrameWork
  GUITestRunner,
  IntStack,
  IntStackTest,
  StringStack,
  StringStackTest,
  IntStackTestViaMixIn,
  StringStackTestViaMixIn
  ;
 
begin
 GUITestRunner.RunRegisteredTests;
end.

StackTest.imp.pas:

{$IfNDef StackTest_imp}
 
{$Define StackTest_imp}
 TEtalonData = ItemsHolder;
 
 _StackTest_ = {mixin} class(TTestCase)
 published
   procedure DoIt;
 protected
 // protected methods
   function GetEtalonData: TEtalonData; virtual; abstract;
   function ArrayToEtalon(const aData: array of _ItemType_): TEtalonData;
     {* Helper function appears since dynamic arrays can be automatically cast to open arrays, but not backwards}
 end;//_StackTest_
 
{$Else StackTest_imp}
 
procedure _StackTest_.DoIt;
var
 l_Etalons : TEtalonData;
 l_S : _StackType_;
 l_I : Integer;
begin
 l_S := _StackType_.Create;
 try
  l_Etalons := GetEtalonData;
  for l_I := Low(l_Etalons) to High(l_Etalons) do
   l_S.Push(l_Etalons[l_I]);
  for l_I := High(l_Etalons) downto Low(l_Etalons) do
   Check(l_S.Pop = l_Etalons[l_I]);
 finally
  FreeAndNil(l_S);
 end;//try..finally
end;
 
function _StackTest_.ArrayToEtalon(const aData: array of _ItemType_): TEtalonData;
var
 l_I : Integer;
begin
 SetLength(Result, Length(aData));
 for l_I := Low(aData) to High(aData) do
  Result[l_I] := aData[l_I];
end;

IntStackTestViaMixIn.pas:

unit IntStackTestViaMixIn;
 
interface
 
uses
  IntStack,
  TestFrameWork
  ;
 
type
 _StackType_ = TIntStack;
 {$Include StackTest.imp.pas}
 TIntStackTestViaMixIn = class(_StackTest_)
 protected
 // realized methods
   function GetEtalonData: TEtalonData; override;
 end;//TIntStackTestViaMixIn
 
implementation
 
uses
  SysUtils
  ;
 
{$Include StackTest.imp.pas}
 
function TIntStackTestViaMixIn.GetEtalonData: TEtalonData;
begin
 Result := ArrayToEtalon([10, 20, 3, 5, 6, 19, 21]);
end;
 
initialization
 TestFramework.RegisterTest(TIntStackTestViaMixIn.Suite);
 
end.


StringStackTestViaMixIn.pas:

unit StringStackTestViaMixIn;
 
interface
 
uses
  StringStack,
  TestFrameWork
  ;
 
type
 _StackType_ = TStringStack;
 {$Include StackTest.imp.pas}
 TStringStackTestViaMixIn = class(_StackTest_)
 protected
 // realized methods
   function GetEtalonData: TEtalonData; override;
 end;//TStringStackTestViaMixIn
 
implementation
 
uses
  SysUtils
  ;
 
{$Include StackTest.imp.pas}
 
function TStringStackTestViaMixIn.GetEtalonData: TEtalonData;
begin
 Result := ArrayToEtalon(['The ', 'cat ', 'sat ', 'on ', 'the ','mat']);
end;
 
initialization
 TestFramework.RegisterTest(TStringStackTestViaMixIn.Suite);
 
end.

We get tests:


As for me, it is cool :-)
Try it. May be you will like it.

It’s clear that we could to the same with native Generic's.

-- and then, may be, we’ll learn to multiply tests by parameterizing on input data. In a special post. We’ll see…

Containers 11. A little about using DUnit

Original in Russian: http://18delphi.blogspot.ru/2013/03/dunit_29.html

About containers. Table of contents

Here we talked about patterns and mixins:
http://18delphi.blogspot.com/2015/02/containers-10-about-patterns-and-mixins.html

Now I’ll tell about the simplest use of DUnit framework.

The reasons WHY we need tests are given briefly here - http://18delphi.blogspot.com/2013/03/blog-post.html .

I think, in future I’ll give “real-world” examples.

Meanwhile, let’s just consider the technique by abstract example.

I will base on classes we’ve got in the previous example.

As usual, we’ll start with the diagram:


SandBox.dpr:

program SandBoxTest;
 
uses
  TestFrameWork
  GUITestRunner,
  IntStack,
  IntStackTest,
  StringStack,
  StringStackTest;
 
 
begin
 GUITestRunner.RunRegisteredTests;
end.

IntStackTest.pas:

unit IntStackTest;
 
interface
 
uses
  TestFrameWork
 
  ;
 
type
 TIntStackTest = {final} class(TTestCase)
 published
 // published methods
   procedure DoIt;
 end;//TIntStackTest
 
implementation
 
uses
  IntStack,
  SysUtils
  ;
 
// start class TIntStackTest
 
procedure TIntStackTest.DoIt;
const
 cEtalons : array [0..3] of integer = (10, 20, 3, 5);
var
 l_S : TIntStack;
 l_I : Integer;
begin
 l_S := TIntStack.Create;
 try
  for l_I := Low(cEtalons) to High(cEtalons) do
   l_S.Push(cEtalons[l_I]);
  for l_I := High(cEtalons) downto Low(cEtalons) do
   Check(l_S.Pop = cEtalons[l_I]);
 finally
  FreeAndNil(l_S);
 end;//try..finally
end;//TIntStackTest.DoIt
 
initialization
 TestFramework.RegisterTest(TIntStackTest.Suite);
 
end.

StringStackTest.pas:

unit StringStackTest;
 
interface
 
uses
  TestFrameWork
  ;
 
type
 TStringStackTest = class(TTestCase)
 published
 // published methods
   procedure DoIt;
 end;//TStringStackTest
 
implementation
 
uses
  StringStack,
  SysUtils
  ;
 
procedure TStringStackTest.DoIt;
const
 cEtalons : array [0..5] of String = ('The ', 'cat ', 'sat ', 'on ', 'the ','mat'); .

var
 l_S : TStringStack;
 l_I : Integer;
begin
 l_S := TStringStack.Create;
 try
  for l_I := Low(cEtalons) to High(cEtalons) do
   l_S.Push(cEtalons[l_I]);
  for l_I := High(cEtalons) downto Low(cEtalons) do
   Check(l_S.Pop = cEtalons[l_I]);
 finally
  FreeAndNil(l_S);
 end;//try..finally
end;//TStringStackTest.DoIt
 
 
initialization
 TestFramework.RegisterTest(TStringStackTest.Suite);
 
end.

-- we’ve got  tests of our classes’ workability :-)

As for me, it is cool. Especially if you run tests EVERY day :-) If we run them every day, we find out on-the-fly what is broken.

Don’t you dare say “it is VERY simple” :-) The devil is always in detail.

I’d try to give more complex examples “taken from life” further.

The main thing is that tests are a very simple way of debugging for many designed classes. Testing eliminates the need for developing a full-scale application wasting 10 minutes for compilation, for deploying database server and so on. One simply thinks up data out of his own head, writes a test and that’s it – he can debug this particular class.

It’s clear that you base on NAMELY THESE data. But nothing prevents us from gradual supplementing tests base and the base of “data from our own head”.

Later I’ll write about how I use mocks basing on etalon files.

Meanwhile read about the idea here:
https://en.wikipedia.org/wiki/Mock_object

If you ask to “show forms testing and GUI”, my answer will be “I’ll certainly show. I’ve got a wealth of experience”.

So far, concentrate on the fact that you should not be afraid to “touch” forms because they “stick out” for user. In contrast, be afraid to deal with base classes, especially if a few tens of different forms use them. Especially if they are written by “a guy who quit the job five years ago”. Especially if you don’t have tests. The more “base character” the class has and the more faults is found, the more tests will be written for it. One day. If you follow my recommendations. It will also become more stable.

Try it. May be you will like it.

The next series is here: http://18delphi.blogspot.com/2015/03/dunit-patterns-and-tests.html


Testing of calculator №7. Comparing of floating-point numbers. Details about tests architecture

Original in Russian: http://programmingmindstream.blogspot.com/2014/06/7.html

Table of contents

Having drawn the diagram of classes to the previous chapter I’ve noticed I also have TRandomPlusTest class which I haven’t seen in GUI of DUnit.



unit RandomPlusTest;
 
interface
 
uses
  PlusTest
  ;
 
type
  TRandomPlusTest = class(TPlusTest)
   protected
    function  GetFirstParam: Single; override;
    function  GetSecondParam: Single; override;
  end;//TRandomPlusTest
 
implementation
 
uses
  TestFrameWork,
  SysUtils
  ;
 
function TRandomPlusTest.GetFirstParam: Single;
begin
 Result := 1000 * Random;
end;
 
function TRandomPlusTest.GetSecondParam: Single;
begin
 Result := 2000 * Random;
end;
 
initialization
 //TestFramework.RegisterTest(TRandomPlusTest.Suite);
 
end.

I’ve looked into the source code and has seen our class is not registered in DUnit. We remove the comment and launch the test.


As you can see, the test has not passed. Let’s look at the details. We select our test and launch it a number of times. Our “random” test does not always fail.


Let’s recollect the classes hierarchy for GUI-testing.


First, let’s look at TFirstTest. It has been directly inherited from TTestCase and it executes one method DoIt.

unit FirstTest;
 
interface
 
uses
  TestFrameWork
  ;
 
type
  TFirstTest = class(TTestCase)
   published
    procedure DoIt;
  end;//TFirstTest
 
implementation
 
procedure TFirstTest.DoIt;
begin
 Check(true);
end;
 
initialization
 TestFramework.RegisterTest(TFirstTest.Suite);
 
end.

We need the first test to “check the work of infrastructure”. In this case, after launching DoIt, we know for sure our test has been registered and it passes.

Then, a more fun architecture begins.
DUnit only launches published procedures (that is it’s character), in which the check is executed. Let’s look closer at our next (first descendant of TTestCase) class TCalculatorGUITest:

unit CalculatorGUITest;
 
interface
 
uses
  TestFrameWork,
  MainForm
  ;
 
type
  TCalculatorGUITest = class(TTestCase)
   protected
    procedure VisitForm(aForm: TfmMain); virtual; abstract;
   published
    procedure DoIt;
  end;//TCalculatorGUITest
 
implementation
 
uses
  Forms
  ;
 
procedure TCalculatorGUITest.DoIt;
var
 l_Index : Integer;
begin
 for l_Index := 0 to Screen.FormCount do
  if (Screen.Forms[l_Index] Is TfmMain) then
  begin
   VisitForm(Screen.Forms[l_Index] As TfmMain);
   break;
  end;//Screen.Forms[l_Index] Is TfmMain
end;
 
end.

As we can see, there’s only published procedure DoIt. Namely it will be executed for all descendants. It will also call the abstract procedure VisitForm, which we have to write in the descendant.
I’d like to give special attention to the fact that we do not register our class in DUnit.

The next is TOperationTest class that implements forms visiting (protected), but it is also not registered in testing framework:

unit OperationTest;
 
interface
 
uses
  CalculatorGUITest,
  MainForm
  ;
 
type
  TOperation = (opAdd, opMinus, opMul, opDiv, opDivInt);
 
  TOperationTest = class(TCalculatorGUITest)
   protected
    procedure VisitForm(aForm: TfmMain); override;
    function  GetOp: TOperation; virtual; abstract;
    function  GetFirstParam: Single; virtual;
    function  GetSecondParam: Single; virtual;
  end;//TOperationTest
 
implementation
 
uses
  TestFrameWork,
  Calculator,
  SysUtils
  ;
 
function TOperationTest.GetFirstParam: Single;
begin
 Result := 10;
end;
 
function TOperationTest.GetSecondParam: Single;
begin
 Result := 20;
end;
 
procedure TOperationTest.VisitForm(aForm: TfmMain);
var
 aA, aB : Single;
begin
 aA := GetFirstParam;
 aB := GetSecondParam;
 aForm.edtFirstArg.Text := FloatToStr(aA);
 aForm.edtSecondArg.Text := FloatToStr(aB);
 case GetOp of
  opAdd:
  begin
   aForm.btnAdd.Click;
   Check((aForm.edtResult.Text) = TCalculator.FloatToStr(aA + aB));
  end;
  opMinus:
  begin
   aForm.btnMinus.Click;
   Check((aForm.edtResult.Text) = TCalculator.FloatToStr(aA - aB));
  end;
  opMul:
  begin
   aForm.btnMul.Click;
   Check((aForm.edtResult.Text) = TCalculator.FloatToStr(aA * aB));
  end;
  opDiv:
  begin
   aForm.btnDiv.Click;
   Check((aForm.edtResult.Text) = TCalculator.FloatToStr(aA / aB));
  end;
  opDivInt:
  begin
   aForm.btnDivInt.Click;
   Check((aForm.edtResult.Text) = TCalculator.FloatToStr(Round(aA) div Round(aB)));
  end;
 end;//case GetOp
end;
 
end.

Finally, we’ve got to the tests. In tests, for example in TPlusTest, we only define the required method GetOp. BUT !!!
We register our test in DUnit.

unit PlusTest;
 
interface
 
uses
  OperationTest
  ;
 
type
  TPlusTest = class(TOperationTest)
   protected
    function  GetOp: TOperation; override;
  end;//TPlusTest
 
implementation
 
uses
  TestFrameWork,
  SysUtils
  ;
 
function TPlusTest.GetOp: TOperation;
begin
 Result := opAdd;
end;
 
initialization
 TestFramework.RegisterTest(TPlusTest.Suite);
 
end.

All we do next for our “pseudo-random” test is we override the procedures of getting the parameters (GetFirstParam, GetSecondParam) and register in DUnit:

unit RandomPlusTest;
 
interface
 
uses
  PlusTest
  ;
 
type
  TRandomPlusTest = class(TPlusTest)
   protected
    function  GetFirstParam: Single; override;
    function  GetSecondParam: Single; override;
  end;//TRandomPlusTest
 
implementation
 
uses
  TestFrameWork,
  SysUtils
  ;
 
function TRandomPlusTest.GetFirstParam: Single;
begin
 Result := 1000 * Random;
end;
 
function TRandomPlusTest.GetSecondParam: Single;
begin
 Result := 2000 * Random;
end;
 
initialization
 TestFramework.RegisterTest(TRandomPlusTest.Suite);
 
end.

Having considered the architecture, let’s get back to our “failure”. As seen from the code above, for the random test we take “any” two numbers (TOperationTest.VisitForm), execute the operation on them using ButtonClick and then compare with the result of addition converted into a string.

Sure, we will not always have the equation. Thing is, many fractional decimal numbers can not be correctly given with nulls and ones of the digital computer.

At this point, we finally get to the core of our article – comparing of floating-point numbers.

This problem has been much discussed. For the first time, I learned about it from the Code complete (12.3. Floating-Point Numbers) by Steve McConnel, although I’ve never faced it in my work. Steve’s example remains actual until now:

program DoubleEqualsExample;
{$APPTYPE CONSOLE}
{$R *.res}
uses
  System.SysUtils;
var
 nominal, sum : double;
 i: byte;
begin
 nominal := 1.0;
 sum := 0;
 for I := 1 to 10 do
  sum := sum + 0.1;
 
 if sum = nominal
  then Writeln('Equals sum=' + FloatToStr(sum) + ' nominal=' + FloatToStr(nominal))
  else Writeln('NOT Equals sum=' + FloatToStr(sum) + ' nominal=' + FloatToStr(nominal));
 
 Readln;
end.

The result of the application’s work:


If we “follow in the master’s steps”, then we’ll print the value sum at each iteration:


As we can see, despite the fact that Delphi rounds our sum to one, it prints another number at the end.
Then, it gets a bit technical. Since I was not the only person to read MacConnel and Delphi developers did read it too, the version of comparing floating-point numbers was taken into account.

Math.pas  unit has such procedures for comparison:
- SomeValue 
- CompareValue
- IsZero

All three functions are intended for comparison with a special accuracy of Epsilon set by user. We check it using our example:

...
 if SameValue(sum, nominal, 0.00000001)
  then Writeln('Equals sum=' + FloatToStr(sum) + ' nominal=' + FloatToStr(nominal))
  else Writeln('NOT Equals sum=' + FloatToStr(sum) + ' nominal=' + FloatToStr(nominal));
...

The result:


The source code of SameValue :

function SameValue(const A, B: Double; Epsilon: Double): Boolean;
begin
  if Epsilon = 0 then
    Epsilon := Max(Min(Abs(A), Abs(B)) * DoubleResolution, DoubleResolution);
  if A > B then
    Result := (A - B) <= Epsilon
  else
    Result := (B - A) <= Epsilon;
end;

We change the comparison for our Random test inherited from TPlusTest:

...
const
 c_Epsilon = 0.0001;
...
  opAdd:
  begin
   aForm.btnAdd.Click;
   Check(SameValue(StrToFloat(aForm.edtResult.Text), (aA + aB), c_Epsilon));
  end;
...

After having launched the test (a number of times), we make sure all is OK:


By analogue we add random of GUI-tests for all operations. I will not give the code because it is quite similar to TRandomTest.

We launch all tests:


All tests except integer division test failed. We correct code of VisitForm, taking the “comparison” into account:


As we can see, there's a problem left with multiplication test. If we multiply “random of a number” from our application in Windows calculator:


we see the error is 1/10.



The comparison of the operations for multiplying with required error:

unit OperationTest;
 
interface
 
uses
  CalculatorGUITest,
  MainForm
  ;
 
type
  TOperation = (opAdd, opMinus, opMul, opDiv, opDivInt);
 
  TOperationTest = class(TCalculatorGUITest)
   protected
    procedure VisitForm(aForm: TfmMain); override;
    function  GetOp: TOperation; virtual; abstract;
    function  GetFirstParam: Single; virtual;
    function  GetSecondParam: Single; virtual;
  end;//TOperationTest
 
implementation
 
uses
  TestFrameWork,
  Calculator,
  SysUtils,
  Math;
 
const
 c_Epsilon = 0.0001;
 c_MulEpsilon = 0.1;
 
function TOperationTest.GetFirstParam: Single;
begin
 Result := 10;
end;
 
function TOperationTest.GetSecondParam: Single;
begin
 Result := 20;
end;
 
procedure TOperationTest.VisitForm(aForm: TfmMain);
var
 aA, aB : Single;
begin
 aA := GetFirstParam;
 aB := GetSecondParam;
 aForm.edtFirstArg.Text := FloatToStr(aA);
 aForm.edtSecondArg.Text := FloatToStr(aB);
 case GetOp of
  opAdd:
  begin
   aForm.btnAdd.Click;
   Check(SameValue(StrToFloat(aForm.edtResult.Text), (aA + aB), c_Epsilon));
  end;
  opMinus:
  begin
   aForm.btnMinus.Click;
   Check(SameValue(StrToFloat(aForm.edtResult.Text), (aA - aB), c_Epsilon));
  end;
  opMul:
  begin
   aForm.btnMul.Click;
   Check(SameValue(StrToFloat(aForm.edtResult.Text), (aA * aB), c_MulEpsilon));
  end;
  opDiv:
  begin
   aForm.btnDiv.Click;
   Check(SameValue(StrToFloat(aForm.edtResult.Text), (aA / aB), c_Epsilon));
  end;
  opDivInt:
  begin
   aForm.btnDivInt.Click;
   Check(SameValue(StrToFloat(aForm.edtResult.Text), (Round(aA) div Round(aB)), c_Epsilon));
  end;
 end;//case GetOp
end;
 
end.

Let’s sum up.

Floating-point numbers will not always be equal, even if they look identical at sight.
Most solutions are provided by standard libraries, so hurry to reinvent the wheel. RTFM :)
In case of two double’s multiplication , find out the accuracy of calculation from the customer.

Some more about our GUI-tests architecture. The final diagram looks like this:


TCalculatorGUITest registers the procedure DoIt for all descendants in DUnit, and it starts the procedure of testing. TOperationTest is actually an abstract class, though it has the whole logic of operations check. Classes - TPlusTest, TMinusTest, ..., etc. are registered in DUnit and are final tests, through the inheritance mechanism. Despite the fact that the whole logic of “correctness check” is in descendant. All Random tests are the expanded version of simple tests, though due to overloading of operations GetFirstParam and GetSecondParam they can act in special cases. In this situation, each class implements pseudo-random input data.

The repository.
p.s.
Useful links:
Numerical methods with FORTRAN iv case studies, W. S. Dorn and D. D. McCracken, Wiley, London, 1972
http://mat.net.ua/mat/biblioteka/McKraken-Dorn-Chislennie-metodi.djvu
http://stackoverflow.com/questions/6106119/how-to-compare-double-in-delphi

Repository