Blog de xerus

Aller au contenu | Aller au menu | Aller à la recherche

mercredi, septembre 29 2010

Eclipse UI unit testing with SWTBot

SWTBot is often used for functional testing. It can also be used for unit testing, where the individual unit of functionality is the UI element we want to test : a control, a cell editor, a dialog, a wizard page ...

Testing a control

Let's say we have a composite control which contains two DateTime controls : one for the date and one for the time.

The code for this control would be something like :

public class DateAndTimeComposite extends Composite {
	private DateTime dateTimeDate;
	private DateTime dateTimeTime;

	public DateAndTimeComposite(Composite parent, int style) {
		super(parent, style);
                [...]
		dateTimeDate = new DateTime(this, SWT.DATE | SWT.MEDIUM);
		dateTimeTime = new DateTime(this, SWT.TIME | SWT.MEDIUM);
                [...]
         }

	public Date getValue() { ...}
	public void setValue(Date date) { }
        [...]
}

To test this control in isolation, we need to

  • create a Shell, set its bounds, set a layout
  • create the control in the shell
  • open the shell
  • activate the shell ...

and all this must be done in the UI thread.

That's what AbstractControlTest does in its openShell method :

public class AbstractControlTest {

	protected SWTWorkbenchBot bot;

	@Before
	public void setupEclipseBot() {
		bot = new SWTWorkbenchBot();
	}

	@After
	public void closeDialog() {
		try {
			bot.shell(getClass().getSimpleName()).close();
		} catch (Exception e) {

		}
	}

	public void activateShell() {
		bot.shell(getClass().getSimpleName()).activate();
	}
	
	protected void openShell(final IControlCreator createControl) {
		openShell(createControl, 200, 50);	
	}
	
	protected void openShell(final IControlCreator createControl, final int width, final int height) {
		UIThreadRunnable.asyncExec(new VoidResult() {
			public void run() {
				Shell shell = new Shell(Display.getCurrent());
				shell.setText(AbstractControlTest.this.getClass()
						.getSimpleName());
				shell.setBounds(100, 100, width, height);
				shell.setLayout(new FillLayout());
				createControl.createControl(shell);
				shell.open();
			}
		});
		bot.shell(getClass().getSimpleName()).activate();
	}

	protected interface IControlCreator {
		public void createControl(Shell shell);

	}

}

By inheriting from this class, our control can be tested without boilerplate code :

public class DateAndTimeCompositeTest extends AbstractControlTest {

	private DateAndTimeComposite dateAndTimeComposite;

	@Test
	public void testDateAndTimeComposite() {
		openShell(new IControlCreator() {

			public void createControl(Shell shell) {
				dateAndTimeComposite = new DateAndTimeComposite(shell, SWT.NONE);
			}

		}, 200, 200);

		bot.dateTime(0).setDate(getDate(2010, 8, 28, 16, 33));
		bot.dateTime(1).setDate(getDate(2010, 8, 28, 16, 33));
		assertEquals(getDate(2010, 8, 28, 16, 33),
				getDateAndTimeCompositeValue());
	}

	private Date getDateAndTimeCompositeValue() {
		Date date = UIThreadRunnable.syncExec(new Result<Date>() {

			public Date run() {
				return dateAndTimeComposite.getValue();
			}
		});
		return date;
	}

	private Date getDate(int year, int month, int day, int hour, int minute) {
		Calendar cal = Calendar.getInstance();
		cal.set(Calendar.YEAR, year);
		cal.set(Calendar.MONTH, month);
		cal.set(Calendar.DAY_OF_MONTH, day);
		cal.set(Calendar.HOUR_OF_DAY, hour);
		cal.set(Calendar.MINUTE, minute);
		cal.set(Calendar.SECOND, 0);
		cal.set(Calendar.MILLISECOND, 0);
		return cal.getTime();
	}

}

We used the custom control directly but you can use the page object pattern as described in Testing Custom Controls. That would avoid the syncExec call in getDateAndTimeCompositeValue().

Testing a dialog

Let's say we have a dialog (the CreateEditRemoteJobDialog class) which is used to edit a job template (whatever it is ...). We want to make sure that the JobTemplate we want to edit is correctly displayed in the dialog. So what we do here is to :

  • open the dialog
  • verify that controls have expected values
public class CreateEditRemoteJobDialogTest extends AbstractDialogTest {
	private CreateEditRemoteJobDialog dialog;
	
	@Test
	public void testCreateRemoteJobDialog() {
		JobTemplate jobTemplate = createJobTemplate();
		
		openDialog(new DialogCreator(jobTemplate));
		 
		bot.text("My job template");
		assertEquals("Admin", bot.textWithLabel("User name :").getText());
		SWTBotTable swtBotTable = bot.table();
		assertEquals(1, swtBotTable.rowCount());
		int row = swtBotTable.indexOf("firstParam", 0);
		assertEquals("22", swtBotTable.cell(row, 1));
	}
	
	private JobTemplate createJobTemplate() {
                // create the job template
                [...]
		return jobTemplate;
	}
	
	private class DialogCreator implements IDialogCreator {
		private JobTemplate jobTemplate;
		
		public DialogCreator(JobTemplate jobTemplate) {
			this.jobTemplate = jobTemplate;
		}
		
		public Dialog createDialog(Shell parentShell) {
			dialog = new CreateEditRemoteJobDialog(parentShell, new ECIServersClientSessionProvider(), jobTemplate); 
			return dialog;
		}

	}	
	
}

Quite easy. The boilerplate code is in AbstractDialogTest :

public class AbstractDialogTest {
	protected SWTWorkbenchBot bot;
	private String shellText = "no shell text";
	
	@Before
	public void setupEclipseBot() {
		bot = new SWTWorkbenchBot();
	}

	@After
	public void closeDialog() {
		try {
			bot.shell(shellText).close();
		} catch (Exception e) {

		}
	}

	public void activateShell() {
		bot.shell(shellText).activate();
	}

	protected void openDialog(final IDialogCreator dialogCreator) {
		UIThreadRunnable.syncExec(new VoidResult() {
			public void run() {
				Shell parentShell = Activator.getDefault().getWorkbench().getActiveWorkbenchWindow().getShell();
				Dialog dialog = dialogCreator.createDialog(parentShell);
				dialog.setBlockOnOpen(false);
				dialog.open();
				shellText =  dialog.getShell().getText();
			}
		});
		bot.shell(shellText).activate();
	}	

	protected void clickOkButton() {
		bot.button("OK").click();
	}
	
	public interface IDialogCreator {
		public Dialog createDialog(Shell parentShell);
	}
	
}

Testing a CellEditor

Testing a CellEditor needs more boilerplate code as we not only need a Shell but also a Table with editing support ...

But we can once again extract it in an Abstract Class.

Once done, testing a CellEditor is quite simple :

public class DateTimeCellEditorTest extends AbstractCellEditorTest{

	@Test
	public void testDateTimeCellEditor() throws Exception {
		setValue(getDate(2009,8,28,15,33));
		openShellWithTable(new ICellEditorCreator() {
			
			public CellEditor createCellEditor(Composite parent) {
				return new DateTimeCellEditor(parent, SWT.NONE);
			}
		});
		bot.table().click(0, 1);
		bot.dateTime(0).setDate(getDate(2010, 8, 28, 16, 33));
		bot.dateTime(1).setDate(getDate(2010, 8, 28, 16, 33));
		bot.table().click(0, 0);
		assertValueEquals(getDate(2010, 8, 28, 16, 33));
	}

[...]
	
}

AbstractCellEditorTest creates a Table with two columns. The second one is editable and will use the CellEditor we want to test. There is only one line. So bot.table().click(0,1) will activate the cell editor

public class AbstractCellEditorTest extends AbstractControlTest {
	private TableViewer tableViewer;
	private NameValue nameValue;

	@Before
	public void initNameValue() {
		nameValue = new NameValue();
	}

	public void setName(String name) {
		nameValue.setName(name);
	}

	public String getName() {
		return nameValue.getName();
	}

	public void setValue(Object value) {
		nameValue.setValue(value);
	}

	public Object getValue() {
		return nameValue.getValue();
	}
	
	public void assertValueEquals(final Object expected) throws TimeoutException {
		Waiter.waitUntil("value changed", new ICondition() {
			
			public boolean test() throws Exception {
				return expected.equals(nameValue.getValue());
			}
		},5000);
	}

	protected void openShellWithTable(final ICellEditorCreator cellEditorCreator) {
		openShell(new IControlCreator() {

			public void createControl(Shell shell) {
				createTable(shell, cellEditorCreator);
			}
		}, 200, 200);
	}

	private void createTable(Composite parent,
			ICellEditorCreator cellEditorCreator) {
		tableViewer = new TableViewer(parent, SWT.BORDER | SWT.H_SCROLL
				| SWT.V_SCROLL | SWT.MULTI | SWT.FULL_SELECTION);

		TableLayout layout = new TableLayout();
		Table table = tableViewer.getTable();
		table.setHeaderVisible(true);
		table.setLinesVisible(true);
		table.setLayout(layout);

		TableViewerColumn column = new TableViewerColumn(tableViewer, SWT.NONE);
		column.setLabelProvider(new ColumnLabelProvider() {
			@Override
			public String getText(Object element) {
				return nameValue.name;
			}
		});
		column.getColumn().setText("Name");
		column.getColumn().setResizable(true);
		layout.addColumnData(new ColumnWeightData(50, true));

		// Target prop
		column = new TableViewerColumn(tableViewer, SWT.NONE);
		column.setLabelProvider(new ColumnLabelProvider() {
			@Override
			public String getText(Object element) {
				return nameValue.value == null ? "null" : nameValue.value
						.toString();
			}
		});
		column.getColumn().setResizable(true);
		column.getColumn().setText("Value");
		column.setEditingSupport(new ValueCellEditingSupport(tableViewer,
				cellEditorCreator));
		layout.addColumnData(new ColumnWeightData(50, true));

		tableViewer.setContentProvider(new ArrayContentProvider());
		tableViewer.setInput(new Object[] { nameValue });
	}

	private class ValueCellEditingSupport extends EditingSupport {

		private ICellEditorCreator cellEditorCreator;

		public ValueCellEditingSupport(ColumnViewer viewer,
				ICellEditorCreator cellEditorCreator) {
			super(viewer);
			this.cellEditorCreator = cellEditorCreator;
		}

		@Override
		protected boolean canEdit(Object element) {
			return true;
		}

		@Override
		protected CellEditor getCellEditor(Object element) {
			return cellEditorCreator.createCellEditor(tableViewer.getTable());
		}

		@Override
		protected Object getValue(Object element) {
			return nameValue.value;
		}

		@Override
		protected void setValue(Object element, Object value) {
			nameValue.value = value;
			tableViewer.refresh();
		}
	}

	public static class NameValue {
		private String name = "name not set";
		private Object value = "value not set";
		
		public synchronized void setName(String name) {
			this.name = name;
		}
		
		public synchronized String getName() {
			return name;
		}
		
		public synchronized void setValue(Object value) {
			this.value = value;
		}
		
		public synchronized Object getValue() {
			return value;
		}
		
	}

	protected interface ICellEditorCreator {
		public CellEditor createCellEditor(Composite parent);

	}

}

Mocking and SWTBot

Don't forget we are doing unit testing here, so you should test the individual UI element and only it. For that, you can mock classes.

Here we mocked IRepositoryProvider using mockito :

public class RepositoryCellEditorTest extends AbstractCellEditorTest {

	@Test
	public void testRepositoryCellEditor() throws ECIException {
		setValue(null);
		
		final IRepositoryProvider repositoryProvider = mock(IRepositoryProvider.class);
		when(repositoryProvider.getRepositories(any(IProgressMonitor.class))).thenReturn(getRepositories());
		
		openShellWithTable(new ICellEditorCreator() {
			
			public CellEditor createCellEditor(Composite parent) {
				return new RepositoryCellEditor(parent, repositoryProvider);
			}
		});
		activateShell();
		bot.table().click(0, 1);
		bot.button("...").click();
		bot.shell("Repository selection");
		bot.table().select("enabled repository");
		bot.button("OK").click();
		bot.table().click(0, 0);
		assertEquals("enabledRepo", bot.table().cell(0, 1));
	}
	
	private Repository[] getRepositories() {
		return new Repository[] {
			aRepository().withDisplayName("disabled repository").withName("disabledRepo").disabled(true).build(),
			aRepository().withDisplayName("enabled repository").withName("enabledRepo").build()
		};
	}
	
}

jeudi, juillet 15 2010

openvpn et HADOPI

Pourquoi utiliser un VPN ?

  • pour surfer de façon anonyme
  • pour utiliser des logiciels de P2P genre amule pour charger une distrib ou autre sans être attendu au tournant
  • pour échapper à la surveillance de son fournisseur d'accès
  • pour montrer l'inutilité d'HADOPI

Les communications entre vous et le fournisseur VPN sont cryptées tandis que celles entre le serveur VPN et internet sont en clair. Concrètement cela permet de remplacer l'adresse IP de votre connexion Internet existante par une adresse IP anonyme à travers un VPN. L'adresse IP que les sites Internet voient et conservent dans leurs bases de données sera celle du fournisseur de VPN et non celle de votre accès Internet. Vous n'êtes donc pas identifiable.

Quel fournisseur choisir ? Je ne vais pas tenter de répondre à cette question, il existe des comparatifs sur le web ou des retours d'expérience sur certains forums. On peut néanmoins citer

Généralement le coût tourne autour de 5 à 10 euros par mois. Pour la suite de ce post, il est nécessaire que le fournisseur supporte OpenVPN (ce qui est très souvent le cas).

OpenVPN, proxy et machine virtuelle

Je désirais utiliser le VPN uniquement pour certaines applications ou lorsque le besoin s'en fait sentir sur le web. En effet, l'utilisation d'un VPN ralentit quelque peu le débit. Plusieurs solutions sont possible :

  • installer openvpn directement sur le serveur. On aura donc deux interfaces connectés au web, Chacune aura une adresse IP différente. Cela revient au même que d'avoir deux fournisseurs internet. Shorewall permet de gérer ce cas.
  • une solution plus simple est d'installer OpenVPN sur une machine virtuelle tournant sur le serveur.

Installation de la machine virtuelle KVM

J'ai utilisé virt-manager pour créer une machine virtuelle KVM tournant sous arch-linux. J'ai choisi virtio pour de meilleures performances.

Pour cela, il faut éditer /etc/mkinitcpio.conf et préciser lors de l'installation :

MODULES="virtio virtio_blk virtio_pci"

Il ne faut pas installer grub à partir du setup (cela ne fonctionne pas directement lorsque l'on utilise virtio). Il est donc nécessaire de quitter le setup de arch. La page suivante du wiki de arch explique ce qu'il est nécessaire de faire.

#nano /boot/grub/device.map
(hd0) /dev/vda
#grub --device-map /boot/grub/device.map
> root (hd0,0)
> setup (hd0)
> quit

Configuration d'openVPN

Sur la machine virtuelle, il est nécessaire de configurer openVPN.

En ce qui me concerne, le fichier client openvpn est le suivant mais il varie en fonction du fournisseur :

/etc/openvpn/vpntunnel.conf

float
client
dev tap
proto udp

; Cert
ca /etc/openvpn/keys/ca.crt
ns-cert-type server
cipher BF-CBC   #Blowfish

;Vpn server

remote-random
remote melissa.vpntunnel.se 1194
remote melissa.vpntunnel.se 10010
remote melissa.vpntunnel.se 10020


;Auth
auth-user-pass vpntunnel_pass.txt

persist-key
persist-tun

; Logging
comp-lzo
verb 1

Le fichier vpntunnel_pass.txt contient le login et mot de passe (chacun sur une ligne).

Configuration du firewall

C'est une étape à ne pas négliger, le firewall installé sur le serveur ne protégera pas la machine virtuelle puisque le flux arrivera crypté jusqu'à elle.

Comme c'est le cas pour le serveur, on peut utiliser shorewall. Pour l'utilisation de shorewall, voir mes précédents billets.

zones

#ZONE   TYPE    OPTIONS                 IN                      OUT
#                                       OPTIONS                 OPTIONS
sec     ipv4

interfaces

#ZONE   INTERFACE       BROADCAST       OPTIONS
sec     tap0            detect          dhcp,tcpflags,nosmurfs,routefilter

policy

#SOURCE         DEST            POLICY          LOG LEVEL       LIMIT:BURST
loc             sec             ACCEPT
$FW             sec             ACCEPT

masq

#INTERFACE              SOURCE          ADDRESS         PROTO   PORT(S) IPSEC   MARK
tap0                    10.0.0.0/8,\
                        169.254.0.0/16,\
                        172.16.0.0/12,\
                        192.168.0.0/16

P2P

Il est maintenant possible d'installer des logiciels de P2P sur la machine virtuelle (bittorent, amule ...).

Amule est disponible dans AUR et peut fonctionner en daemon sans nécessiter l'installation de X11.

Le démarrage d'openvpn se fait avec la commande suivante :

#sudo openvpn /etc/openvpn/vpntunnel.conf

ifconfig permet de vérifier qu'une interface tap0 a bien été créée avec l'ip fourni par openvpn.

#ifconfig

Proxy pour le web

On peut utiliser squid. Son installation est très simple et est détaillée sur le wiki de arch. Pour surfer de façon anonyme sur le web, il suffit alors de configurer son browser pour utiliser le proxy (port 3128 pour squid par défaut).

mercredi, mai 12 2010

Shorewall et Traffic shapping

Le traffic shapping permet de donner plus de priorité à certains paquets (dans mon cas les paquets correspondant au protocole http et ssh) en retardant si nécessaire les autres paquets (typiquement le p2p). Cela permet d'avoir une navigation confortable, des downloads rapides.

Cela fait un bon moment que j'ai mis cela en place mais je n'étais pas sûr que cela changeait vraiment quelque chose.

Mais j'ai mis à jour shorewall hier et cela a eu pour conséquence d'écraser les fichiers de configuration liés a traffic shapping. Et là j'ai senti la différence ...

Pour le reste de la configuration shorewall, se référer à mes billets précédents :

Configuration,

Se référer à la documentation shorewall correspondante

Il y a plusieurs méthodes pour le traffic shapping dans shorewall. On va choisir la plus simple :

Dans shorewall.conf :

TC_ENABLED=Simple

Il faut préciser l'interface réseau concernée (celle connecté internet). Dans mon cas :

Dans /etc/shorewall/tcinterfaces

#INTERFACE             TYPE          IN-BANDWIDTH
eth0                   External

Et enfin on fixe les priorités :

Dans /etc/shorewall/tos :

#SOURCE         DEST            PROTOCOL        SOURCE  DEST    TOS     MARK
#                                               PORTS   PORTS
all             all             tcp             -       80      16      -
all             all             tcp             -       22      16      -
all             all             tcp             -       4662    8       -
all             all             udp             -       4665    8       -
all             all             udp             -       4672    8       -

Ici on a défini le champs TOS (Type of Service) pour un certain nombre de cas. Pour http (port 80) et ssh (port 22), on choisit d'être le plus réactif possible.

Le champs TOS peut avoir les valeurs suivantes :

  • tos-minimize-delay (16)
  • tos-maximize-throughput (8)
  • tos-maximize-reliability (4)
  • tos-minimize-cost (2)
  • tos-normal-service (0)

Il doit également être possible de combiner ces valeurs, voir le tableau dans la documentation shorewall

Lorsque l'on ne précise pas le TOS, tos-normal-service est utilisé.

mercredi, avril 14 2010

Transmorph 3.1.0

I released transmorph 3.1.0.

Transmorph is a free java library used to convert a Java object of one type into an object of another type (with another signature, possibly parameterized). It has no dependencies and is quite easy to use.

In this version, among other changes, I also added NumberComparator.

You may already asked yourself why java.lang.Number does not implement Comparable. Wrapper classes Long, Integer, Byte, Float, Double, BigInteger and BigDecimal implement Comparable, This means that an Integer is comparable to an Integer, a Long to a Long but you can't compare Longs with Integers.

While there are some good reasons, it makes such comparaisons tricky as it requires conversion to a common type.

That's where Transmorph comes in. NumberComparator determines a common subtype of Number between the two numbers (Integer for Byte and Integer, BigDecimal for Float and Integer ...) and uses Transmorph to convert the numbers to this common type.

[java]
NumberComparator numberComparator = new NumberComparator();
assertTrue(numberComparator.compare(12, 24) < 0);
assertTrue(numberComparator.compare((byte) 12, (long) 24) < 0);
assertTrue(numberComparator.compare((byte) 12, 24.0) < 0);
assertTrue(numberComparator.compare(25.0, 24.0) > 0);
assertTrue(numberComparator.compare((double) 25.0, (float) 24.0) > 0);
assertTrue(numberComparator.compare(new BigDecimal(25.0), (float) 24.0) > 0);

samedi, mars 20 2010

backup du serveur avec backuppc

Dans le post précédent, j'ai expliqué comment on pouvait sauvegarder des postes en utilisant backuppc.

Il est également intéressant de sauvegarder certains répertoires du serveur lui-même avec backuppc. Ce n'est pas l'idéal bien-sûr car en cas de crash du serveur, on ne pourra pas utiliser la sauvegarde ... Mais cela a néanmoins un sens en cas de suppression accidentelle de fichiers ou si le serveur est pourvu de plusieurs disques durs.

La page suivante de la documentation de backuppc explique les différentes solutions possibles.

On pourrait utiliser ssh de la même façon que précédemment mais ssh encrypte le traffic, ce qui n'est pas très utile en local. D'autre part, on a configuré sshd pour qu'un seul utilisateur puisse se connecter au serveur. Il faudrait donc rajouter backuppc à cette liste dans l'option AllowUsers dans /etc/ssh/sshd_config.

Plutôt que de faire çà, on va plutôt utiliser tar comme méthode de transmission.

Utilisation de tar

  • On crée un hote, "bump" dans mon cas.
  • On choisit tar comme méthode de transfert
  • On configure les fichiers à sauvegarder. Il faut bien sûr faire attention de ne pas sauvegarder les backups eux-mêmes sinon çà risque de durer un bon moment ...

backuppc server

La documentation précise d'utiliser la commande

 $Conf{TarClientCmd} = '/usr/bin/sudo $tarPath -c -v -f - -C $shareName+ --totals';

Mais cela ne marche pas tout à fait. Les fichiers sont correctement transférés mais tar renvoie un code d'erreur et le backup se finit par un "Got fatal error during xfer".

Après quelques recherches, le post suivant indique la commande à utiliser :

 $Conf{TarClientCmd} = '/usr/bin/sudo LANGUAGE=EN $tarPath -c -v --total -f - -C $shareName+ ';

Il faut aussi permettre à backuppc d'utiliser tar en tant que root :

#sudo visudo
 backuppc ALL = NOPASSWD: /bin/tar

dimanche, mars 14 2010

Serveur personnel avec arch-linux - Partie 4 : les backups

J'ai longtemps cherché un logiciel permettant de faire des sauvegardes de mes différents postes de façon efficace. J'ai utilisé pendant longtemps rsnapshot mais il ne convient pas très bien lorsque certains postes ne sont pas allumés en permanence. Backup-pc semble bien répondre à mes attentes :

  • utilisation d'un pool de fichiers qui utilise des hardlinks. Ainsi il n'y a pas de place perdue lorsque des fichiers sont identiques d'une sauvegarde à l'autre (y compris s'il s'agit de la sauvegarde d'un poste différent),
  • compression des fichiers
  • prend en charge les postes qui ne sont connectés que de façon intermittente au réseau (parce qu'ils sont éteints par exemple ou s'il s'agit de portables)
  • utilisation de rsync ou de smb

Installation du package

La version présente sur aur est à présent la dernière disponible. On l'installe :

#pacman -S backuppc

Un utilisateur backuppc a été créé et deux services sont à présents disponibles :

  • /etc/rc.d/backuppc
  • /etc/rc.d/backuppc-httpd : serveur httpd rien que pour l'interface web de backuppc. Il est configuré pour utiliser le port 81 et peut être démarré en parallèle du serveur apache standard sur le port 80.

Pourquoi ne pas utiliser le serveur apache existant ? Malheureusement, backuppc nécessite qu'apache soit exécuté avec les droits de l'utilisateur backuppc. Pour ceux que cela dérange, il y a une autre solution décrite dans la doc et qui utilise le bit suid sur le script perl.

On peut maintenant démarrer les deux services

 /etc/rc.d/backuppc start
 /etc/rc.d/backuppc-httpd start

puis aller sur la page de l'interface web de BackupPC : http://bump:81/cgi-bin/BackupPC_Admin dans mon cas. En fait, on ne peut pas faire grand chose à présent car BackupPC nécessite que des utilisateurs soient créés pour l'interface web.

On n'a plus qu'à stopper les deux services (c'était bien la peine ... ;-) )

 /etc/rc.d/backuppc stop
 /etc/rc.d/backuppc-httpd stop

Configuration du serveur web

Backuppc utilise le fichier de configuration /etc/httpd/conf/backuppc-httpd.conf.

Il y a quelques lignes à décommenter à la fin du fichier :

 <Directory /opt/BackupPC/www/cgi-bin>
   Order allow,deny
   Allow from all
   Options Indexes FollowSymLinks
   #SetHandler perl-script
   #PerlResponseHandler ModPerl::Registry
   #PerlOptions +ParseHeaders
   Options +ExecCGI
   Setenv REMOTE_USER www
   #Order deny,allow
   #Deny from all
   #Allow from 192.168.1
 AuthName "Backup Admin"
 AuthType Basic
 AuthUserFile /etc/BackupPC/BackupPC.users
 Require valid-user
 </Directory>

On peut à présent ajouter un utilisateur admin (il pourra lancer les backups de n'importe quel hote et aura accès à tous les hotes)

 sudo htpasswd -c /etc/BackupPC/BackupPC.users admin

On édite /etc/BackupPC/config.pl et on modifie

 $Conf{CgiAdminUsers}     = 'admin';

On peut maintenant démarrer les deux services (cette fois-ci c'est la bonne ...)

 /etc/rc.d/backuppc start
 /etc/rc.d/backuppc-httpd start

Configuration de backuppc

méthode de transfert

On peut maintenant utiliser l'interface web pour configurer backuppc. Tout d'abord, on va choisir rsync comme méthode par défaut pour le transfert. On peut toujours utiliser une méthode autre (samba pour les postes windows) pour un hôte donné. Cela se passe dans "Edit Config/Xfer" Ajouter l'option --one-file-system à RsyncArgs et à RsyncRestoreArgs (comme çà on est sûr de ne sauvegarder qu'un filesystem à la fois et on ne risque pas de tenter de sauvegarder /proc)

Hotes

On va ajouter un hote à sauvegarder. Cela se passe dans "Edit hosts" Pour l'hôte, chez moi il s'agit de "antec". Il n'est pas utile de préciser DHCP même si c'est le cas. Dans la colonne user, on peut mettre les utilisateurs qui peuvent avoir accès aux backups de l'hote en plus de l'admin.

Maintenant sélectionner l'hôte antec dans la combo puis "Edit Config/Xfer".

On ajoute les points de montage dans RsyncShareName, dans mon cas

  • /
  • /mnt/ext3-500

puis les répertoires à sauvegarder dans BackupFilesOnly. Pour la clé, on utilise le même nom que pour RsyncShareName

  • /
    • etc
  • /mnt/ext3-500
    • internet/mozilla-profile

Pour être clair :

backupFiles

authentification par clé RSA pour l'accès SSH

C'est bien beau mais il fait comment backuppc pour accéder aux fichiers de l'hôte ? L'utilisateur backuppc se connecte en root sur la machine en question grâce à sa clé privée RSA.

On génère les clés publiques et privés (sans préciser de passphrase)

#sudo su - backuppc
#ssh-keygen -t rsa
#cat .ssh/id_rsa.pub

Ajouter la clé publique sur la machine hôte :

#sudo vim /root/.ssh/authorized_keys
 from="192.168.1.254" ssh-rsa AB3NzaC1yc2EAzYABIwAb... backuppc@bump

L'utilisation de from permet d'améliorer encore la sécurité de la connexion par clé RSA car elle restreint l'utilisation de la clé d'authentification de backuppc à l'adresse IP du serveur de sauvegarde.

Essayons de se connecter à l'hote à partir du serveur de sauvegarde (toujours dans la session de backuppc)

 #ssh root@antec

Et voilà, backuppc va pouvoir faire ses sauvegardes ...

Liens

Quelques liens qui m'ont bien aidé :

samedi, mars 13 2010

backuppc

J'ai mis à jour le package arch-linux pour une version récente de backuppc : backuppc-3.2.0beta1

Pour le moment, il n'est pas possible de le mettre à jour sur aur tant qu'il n'a pas été abandonné par son mainteneur.

D'ici peu, un article concernant ce logiciel de gestion de backup !

Pour l'installer : décompresser le fichier puis :

#makepkg
#sudo pacman -U backuppc-3.2.0beta1-1-x86_64.pkg.tar.gz

vendredi, février 19 2010

P2P anonyme avec I2P et iMule

Dixit wikipedia, I2P (« Invisible Internet Project ») est un réseau anonyme, offrant une simple couche logicielle que les applications peuvent employer pour envoyer de façon anonyme et sécurisée des messages entre elles.

I2P est donc un socle permettant à des logiciels de bénéficier de cet anonymat : c'est le cas de iMule, un client de la famille des *Mule mais anonyme, chiffré, et décentralisé.

Nous allons nous intéresser à l'installation de I2P et de iMule sous Arch linux.

Quelques liens

Les liens suivants m'ont aidé lors de la configuration d'I2P :

et pour iMule :

Installation de I2P

I2P est présent dans AUR, j'ai adopté le package dernièrement et je l'ai mis à jour.

#pacman -S i2p

On peut alors démarrer le service

#/etc/rc.d/i2prouter start

Par défaut la console I2P n'est accessible que depuis la machine locale. Editer le fichier /opt/i2p/clients.config et changer la ligne suivante :

 clientApp.0.args=7657 0.0.0.0 ./webapps/

Il est alors possible d'accéder à la console i2p depuis un autre poste : http://myserver:7657/config.jsp

Firewall

I2P supporte UPnP et Shorewall également mais comme expliqué sur la page correspondante de la documentation de Shorewall, UPnP est un désastre concernant la sécurité puisqu'il permet à des logiciels (ou des trojans) d'ouvrir eux-mêmes des ports du firewall. On va donc s'en passer ...

I2P utilise le port 123 en udp ainsi qu'un autre port aléatoire choisi lors du premier démarrage. Il faut donc tout d'abord relever ce port dans la console i2p

Relever le n° de port dans la console d'i2p :

port UDP i2p

Par défaut, le même port est utilisé en TCP. Il faut donc rajouter à /etc/shorewall/rules :

 #i2p
 ACCEPT          net             $FW             udp     123
 ACCEPT          net             $FW             udp     9315
 ACCEPT          net             $FW             tcp     9315

Après redémarrage de shorewall, i2p devrait afficher un "OK" dans la console d'administration :

firewall i2p ok

Utilisation de privoxy

Pour pouvoir accéder aux sites i2p, il faut utiliser le proxy i2p. Plutôt que de l'utiliser pour toutes les requêtes, on peut utiliser privoxy pour n'utiliser le proxy i2p que pour les domaines .i2p

#pacman -S privoxy

Editer /etc/privoxy/config et modifier

 listen-address  192.168.1.254:8118

de façon à ce que le proxy soit accessible depuis les autres machines du réseau (192.168.1.254 est l'IP de notre firewall)

Puis ajouter, toujours au même fichier :

 #this forwards all requests to .i2p domains to the local i2p
 #proxy without dns requests
 forward .i2p localhost:4444

Configurer le navigateur pour utiliser le proxy 192.168.1.254:8118

Imule

J'ai également créé un package dans aur pour Imule. Imule ne permet pas encore d'être lancé en tant que service headless comme amule. Il est donc nécessaire de l'installer sur une machine disposant de XOrg (ce qui n'est pas le cas de mon firewall). En fait je l'ai installé sur une machine virtuelle présente sur le serveur.

Pour pouvoir utiliser iMule, il y a un certain nombre de changements à faire au niveau de la console I2p. Tout d'abord, il faut activer SAM application bridge et puisque I2P ne tourne pas sur le même serveur que iMule, utiliser 0.0.0.0 plutôt que 127.0.0.0. Pour cela cliquez sur "Services I2P" dans la console I2P.

Config client I2P

Pour fonctionner, iMule doit connaître quelques noeuds du réseau iMule/I2P. Il va pour cela charger un fichier nodes.dat présent sur internet ou sur le réseau I2P. Il est donc nécessaire qu'iMule puisse utiliser le proxy i2p. Puisque iMule n'est pas sur le même serveur qu'I2P, il est nécessaire de remplacer 127.0.0.0 par 0.0.0.0 pour le proxy. Pour cela cliquer sur "Destinations locales" dans la console I2P. Configurer le proxy http I2P de façon à utiliser l'interface 0.0.0.0 :

proxy Http I2P

Dans iMule, j'ai utilisé le fichier dat suivant : http://mkex6401.free.fr/divers/nodes.dat

iMule

jeudi, février 4 2010

transmorph 3.0.2

I released a new version of transmorph.

Transmorph is a free java library used to convert a Java object of one type into an object of another type (with another signature, possibly parameterized).

With transmorph, you can easily convert from one type to another (with just one line):

[java]
convertedType = transmorph.convert(sourceObject, destinationType)

where destinationType can be a class, a Type, a TypeReference (parameterized class), a type signature...

For example :

 [java]
Transmorph converter = new Transmorph(new DefaultConverters());
int[][] arrayOfArrayOfInts = new int[][] { { 11, 12, 13 },{ 21, 22, 23 }, { 31 } };
String[][] arrayOfArrayOfStrings = converter.convert(arrayOfArrayOfInts, String[][].class);

We converted a multidimensional array of ints to a multidimensional array of Strings.

You can also convert to parameterized types (using TypeReference to overcome type erasure):

 [java]
Map map = new HashMap();
map.put("key1", new String[] { "value1-1", "value1-2" });
map.put("key2", new String[] { "value2-1", "value2-2" });
map.put("key3", null);
map.put("key4", new String[] { null, null });
map.put(null, new String[] { "value5-1", "value5-2" });
Map<String, List<String>> converted = converter.convert(map,new TypeReference<Map<String, List<String>>>() {});



DefaultConverters is a set of default converters but you can use the converters you want and write your own. There are actually more than 40 converters including :

   * BeanToBean and MapToBean
   * MapToMap, CollectionToCollection, CollectionToArray
   * EnumToEnum,StringToEnum
   * ...

You can check our junit tests for samples for each converter.

With transmorph, you can also :

  1. parse a type signature ("Map<String,List<String>>" or "Ljava.util.Map<Ljava.lang.String;Ljava.util.List<Ljava.lang.String;>;>;" and use it for conversion
  2. convert and inject properties into a bean (from a Map, another bean ...) using TransmorphBeanInjector

See out web page at http://transmorph.sourceforge.net

vendredi, janvier 29 2010

commandes shorewall

Dans la première partie de mon expérience avec arch-linux, j'ai indiqué comme j'utilisais shorewall. Quelques commandes shorewall sont assez intéressantes.

Utilisation de shorewall en ligne de commande

La première fois que je l'ai fait, c'était pour autoriser à nouveau mon poste client à accéder au serveur parce que je m'étais fait momentanément bannir par fail2ban (pour tester) ! La commande (sur le serveur)

 #shorewall allow ip banned

m'a tiré d'affaire.

Seulement, je me suis rendu compte que la plupart des commandes shorewall ne fonctionnaient pas et retournaient l'erreur suivante :

   LOGFILE (/var/log/shorewall.log) does not exist!

En fait, sous arch, syslog-ng est configuré pour que les logs de iptables aillent dans /var/log/iptables.log

Le plus simple est de modifier dans /etc/shorewall/shorewall.conf :

   LOGFILE=/var/log/iptables.log

Quelques commandes intéressantes

La commande

 #shorewall hits
   HITS DATE
   ---- ------
  24198 Jan 26
  18853 Jan 28
   3832 Jan 27
   2025 Jan 24
   1582 Jan 25
    934 Jan 29

   HITS  PORT SERVICE(S)
   ---- ----- ----------
    538  1433 ms-sql-s  
   1441   500 isakmp    
    139  1080 socks     
      9 10000 webmin    
     82  1025           
     46  8080 webcache  
      4 51198           
     38  1434 ms-sql-m  
      3 22165           
     19  3306 mysql     
      2   130
     18    23 telnet
      7    25 smtp
      4    53 domain
      4    21 fsp,ftp

Pour avoir la liste des macros disponibles :

 #shorewall show macros

Pour connaitre les fonctionnalités iptables disponibles :

 #sudo shorewall show capabilities

Je vous laisse découvrir les autres commandes !

mardi, janvier 12 2010

Serveur personnel avec arch-linux - Partie 3 : virtualisation

Dans la première partie, on a configuré le réseau (firewall, dnsmasq, bridge). Dans la seconde partie, on s'est intéressé à SSH.

Dans cette partie, on va utiliser libvirt et kvm pour gérer des machines virtuelles.

Installation

#yaourt libvirt

On va utiliser les permissions unix plutôt que polkit (pas trop de succès avec polkit) donc il faut décommenter la ligne concernant les droits unix comme indiqué dans le PKGBUILD. On va également installer kvm

#yaourt qemu-kvm

et quelques dépendances :

#pacman -S hal  avahi

Modifier /etc/libvirt/libvirt :

unix_sock_group = "libvirt"
unix_sock_ro_perms = "0770"
unix_sock_rw_perms = "0770"
auth_unix_ro = "none"
auth_unix_rw = "none"

On va crée le groupe libvirt (qui aura accès à libvirt)

#sudo groupadd libvirt
#sudo gpasswd -a cedric libvirt

et on démarre le service :

#sudo /etc/rc.d/libvirtd start

Configuration pour l'accès à distance

Il y a plusieurs possibilités :

  • par SSH : testé avec succès. Ce n'est pas très facile à mettre en place (nécessite l'utilisation de clés SSH)
  • Utiliser TCP/IP : testé avec succès. Facile à mettre en place. J'ai décrit cette méthode sur le wiki Using unencrypted TCP/IP socket
  • utiliser des certificats TLS. Cette méthode est très bien décrite dans la doc de libvirt Remote_certificates. C'est également la méthode que je vais décrire ici.

Utilisation de certificats TLS

#yaourt gnutls

Il est nécessaire de démarrer le serveur avec l'option listen. Editer /etc/conf.d/libvirtd

LIBVIRTD_CONFIG=
LIBVIRTD_ARGS="--listen"
KRB5_KTNAME=/etc/libvirt/krb5.tab

Il faut ensuite générer :

  • un certificat CA auto-signé
  • le certificat du serveur
  • un certificat par client

Certificate Authority (CA) :

Génération de la clé privé :

 #certtool --generate-privkey > cakey.pem

Génération du certificat Créer ca.info

   cn = my home
   ca
   cert_signing_key
 #certtool --generate-self-signed --load-privkey cakey.pem --template ca.info --outfile cacert.pem

Copier le certificat dans /etc/pki/CA/cacert.pem sur le serveur et sur les clients

Certificat du serveur

Génération de la clé privé :

 #certtool --generate-privkey > serverkey.pem

Génération du certificat Créer server.info

   organization = my home
   cn = bump
   tls_www_server
   encryption_key
   signing_key

Signer le certificat avec la clé de la certificate autority

 #certtool --generate-certificate --load-privkey serverkey.pem --load-ca-certificate cacert.pem --load-ca-privkey cakey.pem --template server.info --outfile servercert.pem

Copier

  • serverkey.pem dans /etc/pki/libvirt/private/serverkey.pem sur le serveur
  • servercert.pem dans /etc/pki/libvirt/servercert.pem sur le serveur

Certificat des clients

Pour chaque client : Génération de la clé privée :

 
 #certtool --generate-privkey > clientkey.pem

Créer le certificat client.info :

   country = FR
   state = Paris
   locality = Paris
   organization = my home
   cn = antec
   tls_www_client
   encryption_key
   signing_key

et le signer :

 #certtool --generate-certificate --load-privkey clientkey.pem --load-ca-certificate cacert.pem --load-ca-privkey cakey.pem --template client.info --outfile clientcert.pem

Copier la clé et le certificat sur le client :

#cp clientkey.pem /etc/pki/libvirt/private/clientkey.pem
#cp clientcert.pem /etc/pki/libvirt/clientcert.pem

Connection à distance

#virsh -c qemu://bump/system

Création d'une VM

On peut utiliser pour celà virtinst

#yaourt virtinst

Création de la VM :

 #virt-install --connect qemu:///system --name seven --ram 1024 --os-type=windows --os-variant=win7 --vnc --vnclisten 0.0.0.0 --disk /var/lib/libvirt/images/seven.img,size=20,format=qcow2 --cdrom /home/cedric/isos/Windows7.iso

On crée ici une VM pour Windows7, avec 1Go de RAM, 20Go de disque dur au format qcow2 et on met l'iso du système comme cdrom.

A noter que l'on peut faire tout çà en utilisant virt-manager à partir du poste client ... Enfin en principe : virt-manager fait planter libvirt chez moi 3 fois sur 4.

On peut ensuite se connecter à la VM à partir d'un autre poste avec virt-viewer

#virt-viewer --connect qemu://bump/system seven

Réseau

Dans la première partie, nous avons créé un bridge (sorte de switch virtuel). Nous allons ici connecter la machine virtuelle à ce bridge.

Sur le serveur :

#sudo EDITOR=nano virsh edit seven

Modifier le fichier :

   <interface type='bridge'>
      <mac address='52:54:00:40:41:ee'/>
      <source bridge='br0'/>
      <target dev='vnet0'/>
   </interface>

Utilisation de rdesktop

Plutôt que d'utiliser virt-viewer, il est préférable de configuration l'accès à distance dans seven et d'utiliser rdesktop. On peut utiliser grdesktop par exemple ou remmina

Snapshots

Sur le serveur :

#cd /var/lib/libvirt/images/

Lister les snapshots

#sudo qemu-img snapshot -l seven2.img

Créer un snapshot :

#sudo qemu-img snapshot -c "snapshot label" seven.img

Revenir à un snapshot :

#sudo qemu-img snapshot -a "snapshot label" seven.img

Reste à voir

Lors du redémarrage du serveur (ce qui ne devrait pas arriver trop souvent), les VMs sont stoppés de façon abrupte. Il serait préférable de suspendre la VM (virsh save) et de la restaurer (virsh restore) au démarrage.

Remarques

Utilisation de SSH

L'utilisation de SSH pour la connection à distance nécessite l'utilisation de clés SSH. Voir Using SSH Keys Il faut également installer openbsd-netcat. netcat est nécessaire pour l'accès par ssh mais libvirt utilise nc et non nc.openbsd. Un lien est donc nécessaire :

#ln -s /usr/bin/nc.openbsd /usr/bin/nc

Plantage de libvirt

En cas de plantage de libvirt et si /etc/rc.d/libvirtd start ne fonctionne pas :

#sudo rm /var/run/libvirtd.pid
#sudo /etc/rc.d/libvirtd start

samedi, janvier 9 2010

Serveur personnel avec arch-linux - Partie 2 : SSH

Notre serveur n'est accessible que par SSH. On veut qu'il soit accessible à la fois par le réseau interne et depuis internet.

Configuration d'openssh

Là encore, il n'y a pas grand chose à ajouter par rapport à la page du wiki consacré à SSH.

Dans /etc/ssh/sshd_config, on fixe

...
PermitRootLogin no
StrictModes yes
LogLevel VERBOSE
AllowUsers myUser
...

histoire d'éviter d'empêcher la connection en tant que root. J'ai laissé le port sur la valeur standard 22. Mais on va mettre en place fail2ban pour éviter les attaques par dictionnaire. LogLevel VERBOSE est nécessaire pour fail2ban justement. Pour plus de sécurité, on ne permet qu'à l'utilisateur myUser de se connecter par ssh.

Il faut ajouter une ligne dans /etc/hosts.allow pour permettre les connections :

sshd: ALL

Dans la première partie, on a déjà configuré le firewall pour accepter les connections SSH, donc rien de plus à faire de ce côté

fail2ban

On va utiliser fail2ban. Lorsqu'un utilisateur (ou un bot SSH) va tenter de se connecter et qu'il va faire trop d'erreurs de mot de passe, il sera banni pour un certain temps. Cela va empêcher les attaques par dictionnaires. Cela n'empêche pas de choisir des mots de passe suffisamment sécurisés.

J'ai créé une entrée sur le wiki de arch concernant fail2ban : fail2ban. Il suffit donc de s'y référer pour configurer fail2ban.

A chaque fois qu'une IP sera bannie, on veut qu'un mail nous soit envoyé. Pour cela, on peut utiliser SSMTP. En ce qui me concerne voici la configuration de ssmtp.conf (remplacer your@mail.org pour votre adresse mail):

#
# /etc/ssmtp.conf -- a config file for sSMTP sendmail.
#
# The person who gets all mail for userids < 1000
# Make this empty to disable rewriting.
root=your@mail.org

# The place where the mail goes. The actual machine name is required
# no MX records are consulted. Commonly mailhosts are named mail.domain.com
# The example will fit if you are in domain.com and you mailhub is so named.
#mailhub=mail
mailhub=smtp.free.fr
# Where will the mail seem to come from?
#rewriteDomain=y
rewriteDomain=mydomain.org
# The full hostname
#hostname=localhost.localdomain
hostname=localhost
#UseTLS=YES

#AuthUser=
#AuthPass=

Pour /etc/ssmtp/revaliases

# sSMTP aliases
#
# Format:       local_account:outgoing_address:mailhub
#
# Example: root:your_login@your.domain:mailhub.your.domain[:port]
# where [:port] is an optional port number that defaults to 25.

root:your@mail.org:smtp.free.fr
cedric:your@mail.org:smtp.free.fr

vendredi, janvier 8 2010

Serveur personnel avec arch-linux - Partie 1 : configuration du réseau

Dans ce post et dans les suivants, je vais présenter comment installer un serveur personnel en utilisant la distribution arch linux. Au menu :

  • réseau : dnsmasq, shorewall ...
  • virtualisation avec libvirt et kvm
  • connection au VPN du boulot
  • ...

Tout d'abord, présentation du serveur. Il s'agit d'un Pentium Dual Core E5400 pour ne pas trop consommer mais suffisamment puissant pour de la virtualisation. Il possède deux interfaces réseaux :

  • une reliée à un switch sur lequel sont raccordés les autres ordinateurs du réseau, l'imprimante etc ... Tout est relié à ce switch et non pas à la freebox.
  • une reliée à la freebox (non configurée en mode routeur ou alors avec l'adresse de la DMZ renseignée à l'adresse de notre serveur).

Le serveur ne possèdera pas d'écran, on y accèdera uniquement par SSH.

Configuration du réseau

Renommer les interfaces réseau

Tout d'abord, je me suis rendu compte que les interfaces réseau changeaient régulièrement de nom durant les reboot (eth0 devenait eth1 et inversement). Pour résoudre ce problème, il suffit d'utiliser udev :

Ajouter à /etc/udev/rules.d/010_netinterfaces.rules

KERNEL=="eth*", SYSFS{address}=="00:26:18:a5:60:18", NAME="eth0"
KERNEL=="eth*", SYSFS{address}=="00:26:18:a5:62:fc", NAME="eth1"

On obtient la macaddress avec ifconfig -a

Réseau

Plutôt que de connecter directement une interface au switch du réseau interne, on va créer un bridge pour la prise en charge future des machines virtuelles. local network

Le bridge est comme un switch virtuel. br0 correspond au switch et à la connection du serveur sur ce switch. Ajouter eth1 au switch est comme brancher le cable du switch réel sur le switch virtuel. L'intérêt du bridge est qu'il sera aussi possible de "brancher" des machines virtuelles à ce switch.

J'utilise yaourt pour récupérer les paquets à la place de pacman

#yaourt bridge-utils
#yaourt dhcpcd

/etc/conf.d/bridges

bridge_br0="eth1"
BRIDGE_INTERFACES=(br0)

/etc/rc.conf

MODULES=(bridge)
eth1="eth1 0.0.0.0 promisc"                                             
eth0="dhcp"                                                             
br0="br0 192.168.1.254 netmask 255.255.255.0 broadcast 192.168.1.255"   
INTERFACES=(eth0 br0 eth1) 

eth0 récupère l'adresse IP donnée par free.

/etc/hosts

#<ip-address>   <hostname.domain.org>   <hostname>
192.168.1.254           bump.mydomain.org  bump

Firewall

J'utilise shorewall. Il est recommandé de se reporter à la documentation shorewall pour la configuration d'un firewall à deux interfaces.

Le mieux est de copier les fichiers d'exemple de /usr/share/shorewall/Samples/two-interfaces dans /etc/shorewall puis de les modifier :

/etc/shorewall/zones

#ZONE   TYPE    OPTIONS                 IN                      OUT
#                                       OPTIONS                 OPTIONS
fw      firewall
net     ipv4
loc     ipv4

On définit trois zones : fw, notre firewall, net qui correspond à internet, loc qui correspond à notre réseau local (futures VM comprises).

/etc/shorewall/interfaces

#ZONE   INTERFACE       BROADCAST       OPTIONS
net     eth0            detect          dhcp,tcpflags,nosmurfs,routefilter
loc     br0            detect          tcpflags,nosmurfs,routefilter,routeback

Pour des explications concernant la colonne OPTIONS, se référer à la page man (man interfaces)

/etc/shorewall/masq

#INTERFACE              SOURCE          ADDRESS         PROTO   PORT(S) IPSEC   MARK
eth0                    10.0.0.0/8,\
                        169.254.0.0/16,\
                        172.16.0.0/12,\
                        192.168.0.0/16

Puisque l'on veut partager la connection internet entre plusieurs machines, il est nécessaire d'utiliser l' IP Masquerading pour les adresses locales non-routables.

/etc/shorewall/policy

#SOURCE         DEST            POLICY          LOG LEVEL       LIMIT:BURST

loc             net             ACCEPT
$FW             net             ACCEPT
$FW             loc             ACCEPT
loc             $FW             ACCEPT
net             all             DROP            info
# THE FOLLOWING POLICY MUST BE LAST
all             all             REJECT          info

Le fichier policy correspond à la politique par défaut pour les connections d'une zone à l'autre. Par défaut on rejette toutes les connections venant du net.

/etc/shorewall/routestopped

#INTERFACE      HOST(S)                  OPTIONS
br0

Histoire de pouvoir accéder au firewall lorsque shorewall est stoppé.

/etc/shorewall/rules

#ACTION         SOURCE          DEST            PROTO   DEST    SOURCE          ORIGINAL        RATE        USER/    MARK                                                                                                 
#                                                       PORT    PORT(S)         DEST            LIMIT       GROUP                                                                                                         
#                                                                                                            
#       Accept DNS connections from the firewall to the network                                              
#                                                                                                            
DNS(ACCEPT)     $FW             net                                                                          
#                                                                                                            
#       Accept SSH connections from the local network for administration
#
SSH(ACCEPT)     loc             $FW
#
#       Allow Ping from the local network
#
Ping(ACCEPT)    loc             $FW

#
# Drop Ping from the "bad" net zone.. and prevent your log from being flooded..
#

Ping(DROP)      net             $FW

ACCEPT          $FW             loc             icmp
ACCEPT          $FW             net             icmp
#
Web(ACCEPT)     net             $FW
SSH(ACCEPT)     net             $FW

On accepte l'accès à partir du net pour quelques protocoles : SSH, Web

Enfin, on fixe dans /etc/shorewall/shorewall.conf

IP_FORWARDING=On
STARTUP_ENABLED=Yes

Il ne faut pas oublier de rajouter shorewall à la liste des services DAEMONS dans le fichier /etc/rc.conf puis à démarrer le service

DAEMONS=(...network shorewall...)

dnsmasq

On termine cette première partie par notre serveur dhcp/cache DNS. Il fournira les adresses IP à nos machines locales. La page wiki sur dnsmasq est plutôt bien faite et je conseille de s'y référer.

/etc/dnsmasq.conf :

interface=br0
bind-interfaces
dhcp-range=192.168.1.100,192.168.1.150,infinite

Notre serveur dhcp fournira des adresses IP comprises en 192.168.1.100 et 192.168.1.150. La durée de vie du bail que le DHCP va attribuer aux clients est ici infinie. Pour notre petit réseau, c'est très bien et cela permettra dans le futur de rediriger le traffic vers un ordinateur du réseau local pour certains protocoles (DNAT ou Port Forwarding).

Ajouter dnsmasq à la liste des services DAEMONS dans /etc/rc.conf

DAEMONS=(...network shorewall dnsmasq...)

Il ne reste plus alors qu'à configurer les clients pour utiliser dhcp.

mardi, septembre 15 2009

Building an RCP application including SWTbot tests, Cobertura coverage

Why this post ?

In this post, I will explain how I build an RCP application using eclipse PDE build. There are several samples on the web but quite often for toy applications with only one plugin and with no tests. This is not a ready-made template but you will perhaps find some good ideas to use in your build project.

The sample RCP application has

  • many features and plugins
  • tests, some of them use SWTBot for testing UI.

We want the build to :

  • set up development environment (provision source and binary artifacts)
  • create an executable application to be used in production
  • launch junit tests (some of which use swtbot) during the build and produce junit report and coverage report
  • create an executable test application that will run all the tests when launched and will produce a junit-tests.xml file with results (this can be useful to test the product in a specific environment)

Choosing a build system

We have the choice between :

However, I chose Eclipse PDE build and not Buckminster for the following reasons :

  • we use ivy at work and ivy is not supported by Buckminster (maven is supported though)
  • although documentation is better than before thanks to the Buckminster book, the examples are still quite basic
  • I do not know how to run tests using Buckminster (SWTBot tests, code coverage). This seems to be a work in progress (see https://bugs.eclipse.org/bugs/show_...)

That said, PDE build system is hard to use and give sometimes cryptic error messages.

Others links

Some other related projects that may be worth trying :

Some doc/presentations :

SwtBot releng project that can be used as sample :

  • http://github.com/ketan/swtbot/tree/master/org.eclipse.swtbot.releng/

Creating products and features

I created two products

  • one for production
  • one for test. This way, we can run the tests no only on the build machine and the developpement machine but also run them in a specific environment.

The production product (defined in cetl-product.product file) contains the following features :

<features>
      <feature id="net.entropysoft.cetl.feature"/>
      <feature id="net.entropysoft.transmorph.feature"/>
      <feature id="net.entropysoft.dashboard.feature"/>
      <feature id="net.entropysoft.jmx.feature"/>
[...]
      <feature id="org.eclipse.wst.xml_ui.feature"/>
      <feature id="org.eclipse.sdk"/>
      <feature id="org.eclipse.gef"/>
      <feature id="org.eclipse.emf.edit.ui"/>
      <feature id="org.eclipse.emf.edit"/>
      <feature id="org.eclipse.emf.ecore"/>
      <feature id="org.eclipse.emf.common"/>
      <feature id="org.eclipse.emf.common.ui"/>
      <feature id="org.eclipse.emf.ecore.edit"/>
      <feature id="org.eclipse.xsd.edit"/>
      <feature id="org.eclipse.xsd"/>
      <feature id="org.eclipse.wst.common.fproj"/>
   </features>

The test product (defined in cetl-product-test.product) contains the same features except that it adds the test plugins, swtbot and everything else needed to run the tests :

<feature id="net.entropysoft.cetl.test.feature"/>
      <feature id="net.entropysoft.cetl.testcomponents.feature"/>
      <feature id="org.eclipse.swtbot"/>
      <feature id="org.eclipse.swtbot.eclipse"/>
      <feature id="org.eclipse.swtbot.eclipse.test"/>

Note that we include org.eclipse.swtbot.eclipse.test feature here. This feature comes from the swtbot "Headless Testing Framework".

Checkout of the plugins and features

All our plugin and features are at the same level

net.entropysoft.cetl.feature
net.entropysoft.cetl.plugin
net.entropysoft.cetl.plugin.test
net.entropysoft.cetl.product
net.entropysoft.cetl.releng
net.entropysoft.cetl.test.feature
...

You can use a team project set to checkout your projects easily. You can then use svnGetProjectSet task from ant4eclipse if you want to checkout your projects from the team project set.

the releng project

the releng project will contain (most of the directories are created during the build) :

baseLocation/ : the target platform (created during the build)
  features/
    org.eclipse.rcp_3.5.0.v20090519-9SA0FwxFv6x089WEf-TWh11/
    ... 
  plugins/
    org.eclipse.core.runtime_3.5.0.v20090525.jar
    ...
eclipse/ : an eclipse install (created during the build)
ivy/ 
  apache-ivy-2.1.0-rc2 : an ivy install (created during the build)
lib/ : the dependencies retrieved by ivy (created during the build)
  bundles/
    slf4j-api.jar
    ...
  test
    cobertura.jar
    ...
  ...
product/
  buildConfiguration/ : configuration directory
    build.properties : copied from org.eclipse.pde.build and modified according to our needs
  buildDirectory/ : directory the build will take place in (created during the build)
product-test/
  buildConfiguration/ : configuration directory for test product
    build.properties : copied from org.eclipse.pde.build and modified according to our needs  
  buildDirectory/ : directory the build will take place in (created during the build)
test/
  cetl-studio-test/
  junit-results/
  workspace/
build-product-test.xml
build-product.xml
build.properties
build.xml
dependencies.xml
ivy.xml
materialize.xml
test.xml

I used the same terms (baseLocation, buildDirectory, buildConfiguration) than in PDE product build

build-product-test.xml, build-product.xml, build.xml, dependencies.xml, materialize.xml and test.xml are ant build files. Build.xml import all the others. This way, we have clear and short xml files instead of one big ant file.

Materializing target platform

The target platform (aka baseLocation) will be used to build the product but will be also used during developpement.

It contains all the pre-built features and plug-ins that our product requires.

ant target

The following ant target will provision the binary artifacts against which to build.

<target name="materializeTargetPlatform" description="materialize the target platform" depends="retrieve-ivy-dependencies">
		<delete dir="${baseLocation}" />
		<unzip dest="${baseLocation}" overwrite="true" src="${eclipse.zip}">
			<mapper type="glob" from="eclipse/*" to="*" />
		</unzip>
		<unzip dest="${baseLocation}" overwrite="true" src="${eclipse-RCP-delta-pack.zip}">
			<mapper type="glob" from="eclipse/*" to="*" />
		</unzip>
		<unzip dest="${baseLocation}" overwrite="true" src="${gef-runtime.zip}">
			<mapper type="glob" from="eclipse/*" to="*" />
		</unzip>
		<unzip dest="${baseLocation}" overwrite="true" src="${emf-runtime.zip}">
			<mapper type="glob" from="eclipse/*" to="*" />
		</unzip>
 
[...Some other dependencies from eclipse.org...]
 
		<unzip dest="${baseLocation}" overwrite="true" src="${swtbot.eclipse.zip}">
			<mapper type="glob" from="eclipse/*" to="*" />
		</unzip>
		<unzip dest="${baseLocation}" overwrite="true" src="${swtbot.eclipse.headless.zip}">
			<mapper type="glob" from="eclipse/*" to="*" />
		</unzip>
 
		<!-- bundles we depends on and that are in ivy repository -->
		<copy overwrite="true" todir="${baseLocation}/plugins">
			<fileset dir="lib/bundles" />
		</copy>
 
		<!-- don't need other directories than plugins and features. And if we create a .target from this dir it will not be correct if
		 we keep other directories -->
		<delete includeemptydirs="true">
			<fileset dir="${baseLocation}">
				<exclude name="plugins/**" />
				<exclude name="features/**" />
			</fileset>
		</delete>
	</target>

It just uncompress eclipse zip file, RCP-delta-pack, SWTBot and some other packages that our product requires.

${eclipse.zip}, ${eclipse-RCP-delta-pack.zip}, ${swtbot.eclipse.zip} ... refer to a corresponding file. I put all these files in a shared directory (And I do not delete them when I use a newer version so that I can rebuild an old version of the product).

Some things that are worth noting :

  • The RCP delta pack is mandatory as it includes the org.platform.launchers.feature which contains the launchers and root files necessary for a product.
  • swtbot.eclipse.zip and swtbot.eclipse.headless.zip are necessary as we use the target platform to build both production product and test product
  • I remove all directories other than plugins and features as it caused problems when I created a .target file

ivy

"materializeTargetPlatform" target depends on "retrieve-ivy-dependencies" because we use ivy at work and some of our dependencies here are managed by ivy.

For the curious persons, here is an excerpt of the ivy file :

<ivy-module version="1.0">
    <info organisation="entropysoft" module="net.entropysoft.cetl.releng">
    	<description homepage="http://www.entropysoft.net">Content ETL</description>
    </info>
    <configurations>
	  <conf name="bundles" visibility="private" transitive="false" description="osgi bundles required for compiling the module. non transitive"/>
	  <conf name="components-plugin-libs" visibility="private" transitive="false" description="libraries required for net.entropysoft.components.plugin. non transitive"/>
	  <conf name="test" visibility="private" description="dependencies required for the test compilation and execution phases."/>
[...]
    </configurations>
	<publications>
	</publications>
    <dependencies>
[...]
        <dependency org="entropysoft" name="components" rev="latest.integration" conf="components-plugin-libs->default"/>
 
        <dependency org="org.slf4j" name="jcl-over-slf4j" rev="1.5.8" conf="bundles->default"/>
        <dependency org="org.slf4j" name="slf4j-simple" rev="1.5.8" conf="bundles->default"/>
        <dependency org="org.slf4j" name="slf4j-api" rev="1.5.8" conf="bundles->default"/>
 
        <!-- we use cobertura to instrument our plugins -->
        <dependency org="net.sourceforge.cobertura" name="cobertura" rev="1.9.2" conf="test->default"/>
    </dependencies>
</ivy-module>

We use slf4j in our product. slf4j is already OSGI compatible. But if you need an an OSGI-ready version of a library, you can try at either :

Using the target platform during developement

See the following article to know why creating a target platform is a good thing : Why create a custom target platform?

I created a .target file and set it as target platform. As location, I choose "installation" and ${project_loc}/../net.entropysoft.cetl.releng.baseLocation (as I put the target file in net.entropysoft.cetl.product)

target definition

Updating the workspace

This step is very specific to your product but you probably have some things to copy, create or modify to have your workspace up and ready.

Here we copy some jars to a plugin and update the help (we use DocBook for the documentation and we have to convert it to eclipse help and to pdf ...)

<target name="materializeWorkspace" description="materialize workspace" depends="retrieve-ivy-dependencies">
		<copy overwrite="true" todir="../net.entropysoft.components.plugin">
			<fileset dir="lib/components-plugin-libs" />
		</copy>
		<antcall target="updateHelpPlugin" />
[...]
	</target>

The developement environment

<target name="setup" depends="materializeTargetPlatform,materializeWorkspace" />

This time, we have all we need to setup our environment. Once eclipse, subclipse and swtbot have been installed, all we need is to import the team project set and to launch the ant build with setup target. After selecting the .target and refreshing all the projects, they should all be able to compile.

Building the product

Create the build directory

First we create the build directory that is the directory the build will take place in. We copy our plugins and features respectively in "plugins" and "features" subdirectories.

The createBuildDirectory macro is defined as :

<macrodef name="createBuildDirectory">
		<attribute name="buildDirectory" />
		<attribute name="buildVersion"/>
		<sequential>
			<delete failonerror="false" includeemptydirs="true">
				<fileset dir="@{buildDirectory}">
					<exclude name="plugins/**/**.*" />
					<exclude name="features/**/**.*" />
				</fileset>
			</delete>
			<sync todir="@{buildDirectory}/features" includeemptydirs="true">
				<fileset dir="../">
					<include name="net.entropysoft.*.feature/**" />
[...]
				</fileset>
			</sync>
			<sync todir="@{buildDirectory}/plugins" includeemptydirs="true">
				<fileset dir="../">
					<include name="net.entropysoft.*.plugin/**" />
					<include name="net.entropysoft.*.plugin.test/**" />
					<include name="net.entropysoft.cetl.product/**" />
					<include name="net.entropysoft.cetl.help/**" />
[...]
					<!-- also exclude the generated class files -->
					<exclude name="*/bin/**" />
				</fileset>
			</sync>
		</sequential>
	</macrodef>

using productBuild.xml from pde build

We then invoke the productBuild.xml provided by pde build (see Building an RCP application from a product configuration file)

<property name="product.buildDirectory" value="${basedir}/product/buildDirectory" />
	<property name="product.buildConfiguration" value="${basedir}/product/buildConfiguration" />
 
	<!-- make sure to call targets materializeTargetPlatform and materializeWorkspace before -->
	<target name="product-build" depends="failIfBaseLocationUnavailable,extract-eclipse" description="creates product">
		<createBuildDirectory builddirectory="${product.buildDirectory}" buildversion="${buildVersion}"/>
		<java classname="org.eclipse.equinox.launcher.Main" fork="true" failonerror="true" dir="${basedir}">
			<arg value="-application" />
			<arg value="org.eclipse.ant.core.antRunner" />
			<arg value="-buildfile" />
			<arg value="${eclipseDirectory}/plugins/org.eclipse.pde.build_${pdeBuildPluginVersion}/scripts/productBuild/productBuild.xml" />
 
			<arg value="-DbaseLocation=${baseLocation}" />
			<arg value="-Dbuilder=${product.buildConfiguration}" />
			<arg value="-Dbasedir=${basedir}" />
			<arg value="-DbuildDirectory=${product.buildDirectory}" />
			<arg value="-DJ2SE-1.5=${jdk15Dir}/jre/lib/rt.jar" />
			<arg value="-DJavaSE-1.6=${jdk16Dir}/jre/lib/rt.jar" />
			<arg value="-DbuildVersion=${buildVersion}"/>
 
			<!-- there is a binary cycle due to slf4j -->
			<arg value="-DallowBinaryCycles=true" />
 
			<classpath>
				<fileset dir="${eclipseDirectory}/plugins">
					<include name="org.eclipse.equinox.launcher_*.jar" />
				</fileset>
			</classpath>
		</java>
	</target>

build configuration

product/buildConfiguration/build.properties is a modified copy of the template build.properties from org.eclipse.pde.build/templates/headless-build

product=net.entropysoft.cetl.product/cetl-product.product
runPackager=true
# The prefix that will be used in the generated archive.
archivePrefix=cetl-studio
# The location underwhich all of the build output will be collected.
collectingFolder=${archivePrefix}
# The list of {os, ws, arch} configurations to build.  This 
# value is a '&' separated list of ',' separate triples.  For example, 
#     configs=win32,win32,x86 & linux,motif,x86
# By default the value is *,*,*
configs=win32, win32, x86
# Type of build.  Used in naming the build output.  Typically this value is
# one of I, N, M, S, ...
buildType=I
# ID of the build.  Used in naming the build output.
buildId=cetl-studio-${buildVersion}
# Label for the build.  Used in naming the build output
buildLabel=cetl-studio 
# Timestamp for the build.  Used in naming the build output
timestamp=007
#Os/Ws/Arch/nl of the eclipse specified by baseLocation
baseos=win32
basews=win32
basearch=x86
filteredDependencyCheck=false
resolution.devMode=false
skipBase=true
skipMaps=true
skipFetch=true
# Specify the output format of the compiler log when eclipse jdt is used
logExtension=.log
# Whether or not to fail the build if there are compiler errors
javacFailOnError=true
# Enable or disable verbose mode of the compiler
javacVerbose=false
extraVMargs=-XX:MaxPermSize=512m
# produce a properly installed, fully p2 enabled, product
p2.gathering=true

extract-eclipse target just creates eclipse directory with a full eclipse installation we use to run the build.

Build the test product

productTest-build target

productTest-build is very similar to product-build. baselocation is still the same but buildDirectory and builder need to be changed.

<target name="productTest-build" depends="failIfBaseLocationUnavailable,extract-eclipse" description="creates test product">
		<createBuildDirectory builddirectory="${product-test.buildDirectory}" buildversion="${buildVersion}" />
		<java classname="org.eclipse.equinox.launcher.Main" fork="true" failonerror="true" dir="${basedir}">
			<arg value="-application" />
			<arg value="org.eclipse.ant.core.antRunner" />
			<arg value="-buildfile" />
			<arg value="${eclipseDirectory}/plugins/org.eclipse.pde.build_${pdeBuildPluginVersion}/scripts/productBuild/productBuild.xml" />
 
			<arg value="-DbaseLocation=${baseLocation}" />
			<arg value="-Dbuilder=${product-test.buildConfiguration}" />
			<arg value="-Dbasedir=${basedir}" />
			<arg value="-DbuildDirectory=${product-test.buildDirectory}" />
			<arg value="-DJ2SE-1.5=${jdk15Dir}/jre/lib/rt.jar" />
			<arg value="-DJavaSE-1.6=${jdk16Dir}/jre/lib/rt.jar" />
			<arg value="-DbuildVersion=${buildVersion}" />
 
			<!-- there is a binary cycle due to slf4j -->
			<arg value="-DallowBinaryCycles=true" />
 
			<classpath>
				<fileset dir="${eclipseDirectory}/plugins">
					<include name="org.eclipse.equinox.launcher_*.jar" />
				</fileset>
			</classpath>
		</java>
		<antcall target="removeSwtbotJunit3Plugins" />
		<antcall target="modifyTestProductIni" />
	</target>

build configuration

What is different is the product-test/buildConfiguration/build.properties file :

product=net.entropysoft.cetl.product/cetl-product-test.product
runPackager=true
archivePrefix=cetl-studio-test
collectingFolder=${archivePrefix}
configs=win32, win32, x86
buildType=I
buildId=cetl-studio-test-${buildVersion}
buildLabel=cetl-studio-test
timestamp=007
baseos=win32
basews=win32
basearch=x86
filteredDependencyCheck=false
resolution.devMode=false
skipBase=true
skipMaps=true
skipFetch=true
logExtension=.log
javacDebugInfo=true
javacFailOnError=true
javacVerbose=false
# Extra arguments for the compiler. These are specific to the java compiler being used.
compilerArg=-g
extraVMargs=-XX:MaxPermSize=512m
p2.gathering=true

javacDebugInfo is set to true and compilerArg is set to -g (this seems to be necessary for cobertura reports to be useful)

Removing swtbot junit3 feature & plugin

The ant target removeSwtbotJunit3Plugins is used to remove org.eclipse.swtbot.eclipse.junit3.headless and org.eclipse.swtbot.ant.optional.junit3 plugins as I use Junit4 in my tests and that these plugins must not be used together with org.eclipse.swtbot.eclipse.junit4.headless and org.eclipse.swtbot.ant.optional.junit4.

This is needed as there is one feature for both swtbot junit3 and junit4 plugins. This may change in next swtbot release.

<target name="removeSwtbotJunit3Plugins">
		<!--  JUnit 3.x and 4.x don't play well together. We remove org.eclipse.swtbot.eclipse.junit3.headless and org.eclipse.swtbot.ant.optional.junit3 from the plugins dir. -->
		<!-- see http://wiki.eclipse.org/SWTBot/Ant -->
		<zip destfile="${product-test.buildDirectory}/tmp.jar">
			<zipfileset src="${product-test.buildDirectory}/cetl-studio-test/cetl-studio-test-${buildVersion}-win32.win32.x86.zip">
				<exclude name="**/org.eclipse.swtbot.eclipse.junit3.headless*/**" />
				<exclude name="**/org.eclipse.swtbot.ant.optional.junit3*.jar" />
			</zipfileset>
		</zip>
		<move file="${product-test.buildDirectory}/tmp.jar" tofile="${product-test.buildDirectory}/cetl-studio-test/cetl-studio-test-${buildVersion}-win32.win32.x86.zip" />
	</target>

Modifying product ini file

This step is necessary if you want that your test product run all the tests when launched and produce a junit-tests.xml file with results. I think this can be useful to test the product in a specific environment.

First I think I could set the program arguments in cetl-product-test.product :

-application
org.eclipse.swtbot.eclipse.junit4.headless.swtbottestapplication
formatter=org.apache.tools.ant.taskdefs.optional.junit.XMLJUnitResultFormatter,junit-results.xml
-testPluginName
net.entropysoft.cetl.plugin.test
-className
net.entropysoft.AllTestsSuite

but I got an exception during build :

java echo !MESSAGE Invalid action syntax: 
addProgramArg(programArg:formatter=org.apache.tools.ant.taskdefs.optional.junit.XMLJUnitResultFormatter,junit-results.xml).
java echo !STACK 0
java echo java.lang.IllegalArgumentException: Invalid action syntax: 
addProgramArg(programArg:formatter=org.apache.tools.ant.taskdefs.optional.junit.XMLJUnitResultFormatter,junit-results.xml).
java echo at 
org.eclipse.equinox.internal.p2.engine.InstructionParser.parseAction(InstructionParser.java:97)

It seems that we cannot have a coma in the arguments otherwise we got this exception (at least with eclipse-3.5, this may be corrected in newer versions).

So I generate the ini file and include it in the test product zip file :

<target name="modifyTestProductIni">
		<!-- We should set the arguments in cetl-product-test.product but we have an error during the build because of the coma in formatter line-->
		<echo file="${product-test.buildDirectory}/cetl-studio-test/cetl-studio.ini">
-application
org.eclipse.swtbot.eclipse.junit4.headless.swtbottestapplication
formatter=org.apache.tools.ant.taskdefs.optional.junit.XMLJUnitResultFormatter,junit-results.xml
-testPluginName
net.entropysoft.cetl.plugin.test
-className
net.entropysoft.AllTestsSuite
-vmargs
-XX:MaxPermSize=512M
-Xms40m
-Xmx512m
		</echo>
		<zip destfile="${product-test.buildDirectory}/cetl-studio-test/cetl-studio-test-${buildVersion}-win32.win32.x86.zip" update="true">
			<zipfileset file="${product-test.buildDirectory}/cetl-studio-test/cetl-studio.ini" prefix="cetl-studio-test" />
		</zip>
	</target>

Running the tests

For the tests, we can use http://github.com/ketan/swtbot/blob... from swtbot. However, I chose here not to use it.

Install the test product

We just uncompress the test product we just created :

<target name="install-product-test">
		<delete dir="${testDirectory}" />
		<unzip src="${product-test.buildDirectory}/cetl-studio-test/cetl-studio-test-${buildVersion}-win32.win32.x86.zip" dest="${testDirectory}" />
	</target>

Instrument the classes to be tested

<target name="instrument-product-test" depends="taskdef-cobertura">
		<mkdir dir="${testDirectory}/cobertura-data" />
		<cobertura-instrument maxmemory="128M" datafile="${testDirectory}/cobertura-data/cobertura.ser">
			<includeClasses regex="net\.entropysoft\..*\.plugin\..*" />
			<instrumentationClasspath>
				<fileset dir="${testDirectory}/cetl-studio-test/plugins">
					<include name="net.entropysoft.*.jar" />
					<include name="net.entropysoft.*/**/*.class" />
[...]
				</fileset>
			</instrumentationClasspath>
		</cobertura-instrument>
	</target>

Generate another ini file for our test

We need to modify the ini file of the product to take the instrumentation into account. All instrumented classes will need to be able to access classes from cobertura.jar, we can do that by adding cobertura.jar to the bootclasspath.

I also set the vm to use.

<target name="generateTestIniFile">
		<echo file="${testDirectory}/cetl-studio-test/cetl-studio.ini">
-data
${testDirectory}/workspace
-application
org.eclipse.swtbot.eclipse.junit4.headless.swtbottestapplication
formatter=org.apache.tools.ant.taskdefs.optional.junit.XMLJUnitResultFormatter,${testDirectory}/junit-results/junit-results.xml
formatter=org.apache.tools.ant.taskdefs.optional.junit.PlainJUnitResultFormatter,${testDirectory}/junit-results/junit-results.txt
-testPluginName
net.entropysoft.cetl.plugin.test
-className
${testSuiteClass}
-nosplash
-suppressErrors
-consolelog
-vm
${jdk15Dir}/bin/java.exe
-vmargs
-Xbootclasspath/p:${basedir}/lib/test/cobertura.jar
-Dnet.sourceforge.cobertura.datafile="${testDirectory}/cobertura-data/cobertura.ser"
-XX:MaxPermSize=512M
-Xms40m
-Xmx512m
		</echo>
	</target>

Running the tests

Just execute the test product with the modified ini file :

<target name="run-tests" depends="generateTestIniFile">
		<delete dir="${testDirectory}/workspace" />
		<mkdir dir="${testDirectory}/junit-results" />
 
		<exec executable="${testDirectory}/cetl-studio-test/cetl-studio.exe" dir="${basedir}" logError="true" failonerror="false" output="${testDirectory}/output.txt"/>
	</target>

reports

Nothing special here :

<target name="junit-report">
		<mkdir dir="${testDirectory}/junit-report" />
		<junitreport todir="${testDirectory}/junit-report">
			<fileset dir="${testDirectory}/junit-results">
				<include name="*.xml" />
			</fileset>
			<report format="frames" todir="${testDirectory}/junit-report" />
		</junitreport>
	</target>
<target name="cobertura-report" depends="taskdef-cobertura">
		<mkdir dir="${testDirectory}/coverage-report" />
		<cobertura-report format="html" datafile="${testDirectory}/cobertura-data/cobertura.ser" destdir="${testDirectory}/coverage-report">
			<fileset dir="../net.entropysoft.cetl.plugin/src" />
[...]
		</cobertura-report>
	</target>

Putting all together

I defined an "all" target to be called by CruiseControl, luntbuild, hudson, continuum or whatever continuous build tool you use :

<target name="all" depends="
		materializeTargetPlatform,
		materializeWorkspace,
		product-build,
		productTest-build,
		install-product-test,
		instrument-product-test,
		run-tests,
		junit-report,
		cobertura-report,
		publish,
		fail-if-errors,
		createSvnTag" />
<target name="fail-if-errors">
		<loadfile property="test.results" srcFile="${testDirectory}/junit-results/junit-results.xml" />
		<fail message="JUnit tests failed!">
			<condition>
				<not>
					<contains string="${test.results}" substring='errors="0" failures="0"' />
				</not>
			</condition>
		</fail>
	</target>

samedi, février 7 2009

Transmorph 2.0.0 released

Transmorph strong points are :

* support conversion for primitives and objects
* support conversion to multidimensional arrays
* support conversion to parameterized collections and types
* support conversion for beans that contain bi-directional relationships
* you can choose exactly which converters you want to use
* jars for JDK 1.4 and JDK 1.5
* no dependencies
* 40 converters included
* easy to add more converters
* objects can be modified after conversion using modifiers
* can convert to a type given either its java type (Class) or signature
* easy to use

Features added in version 2.0.0 (February 3, 2009)

 *  support for dotted style signatures (Ljava.util.Map<Ljava.lang.String;Ljava.util.List<Ljava.lang.String;>;>;)
 * new conversion context.
     It contains a pool of created objects to support data objects that contain bi-directionalrelationships.
     It also contains the used converter (useful for debugging)
 * new converters (BeanToBean, EnumToEnum, ImmutableIdentityConverter, StringToQName, StringToTimeZone)
 * added the notion of modifiers than can be applied after conversion : CanonicalizeFile, LowerCaseString, ResolveFile, TrimString, UppercaseString
 * many converters improved

vendredi, janvier 9 2009

transmorph 1.0.0 released : Another Object Conversion Framework

Transmorph is a new free java library (Apache 2 License) I created to convert a Java object of one type into an object of another type (with another signature, possibly parameterized).

Transmorph strong points are :

  • support conversion for primitives and objects
  • support conversion to multidimensional arrays
  • support conversion to "parameterized collections" and types
  • jars for JDK 1.4 and JDK 1.5
  • no dependencies
  • easy to add more converters
  • can convert to a type given either its java type (Class) or signature

Once configured, you can do the following conversions, for example :

// int[] to a List<Integer>
List<Integer> listOfInts = (List<Integer>) converter.convert(new int[] { 0, 1, 2, 3, 4, 5 },   List.class, new Class[] { Integer.class });
 
// Map<String,String[]> to a Map<String, List<String>
Map<String, List<String>> converted = (Map<String, List<String>>)converter.convert(map,"Ljava/util/Map<Ljava/lang/String;Ljava/util/List<Ljava/lang/String;>;>;");
 
// int[] => LinkedList<Integer> (ArrayToListConverter)
LinkedList linkedList = (LinkedList) converter.convert(new int[] { 0, 1, 2, 3, 4, 5 },   LinkedList.class,  new Class[] { Integer.class });
 
// int[][] => String[][] (ArrayToArrayConverter)
int[][] arrayOfArrayOfInts = new int[][] { { 11, 12, 13 },   { 21, 22, 23 }, { 31 } };
String[][] arrayOfArrayOfStrings = (String[][]) converter.convert(arrayOfArrayOfInts,  (new String[0][0]).getClass());

lundi, décembre 29 2008

IsbnExtractor : now on sourceforge

ISBNExtractor is now on sourceforge.

This site describes it and explain how to use it : http://isbnextractor.sourceforge.net

Here is a sample to use the library to get the isbn from a pdf file :

File file = new File(pdfFilePath);
FileISBNExtractor fileISBNExtractor = new FileISBNExtractor();
ISBNCandidates isbnCandidates = fileISBNExtractor.getIsbnCandidates(file);
ISBN isbn = isbnCandidates.getHighestScoreISBN();

mercredi, novembre 26 2008

PdfIsbnExtractor

Some time ago, I developed PdfIsbnExtractor. This is a program which can extract the isbn(s) from a pdf file. There are generally several candidates so each isbn candidate is given a score depending of the location of the isbn in the book, the isbn format ... It uses either pdttotext or pdfbox) to extract text from the pdf.

For now pdfIsbnExtractor is used as a plugin to Tellico)

You can get it at http://www.kde-files.org/content/show.php/PdfIsbnExtractor?content=90081

dimanche, novembre 23 2008

Mise à jour vers dotclear2

J'ai mis à jour le blog vers dotclear2. Comme il n'existait pas d'ebuild pour gentoo, j'en ai créé un pour l'occasion.

Voir http://bugs.gentoo.org/show_bug.cgi?id=248296

samedi, octobre 18 2008

Mise à jour du site de WinHP

WinHp est un logiciel que j'avais réalisé il y a pas mal de temps et auquel je n'ai plus touché depuis 2002. Il permet d'éditer des fichiers pour HP48/49/50. Il a eu et a encore pas mal de succès (au Brésil notamment) : 123353 downloads à ce jour si j'en crois sourceforge.

Du coup, j'ai mis à jour le site web et j'utilise maintenant un wiki : mediawiki. C'est ainsi plus facile pour le mettre à jour et pour que les utilisateurs puissent participer.

La page de WinHP : http://winhp.sourceforge.net/wiki/index.php/Main_Page

- page 1 de 2