view test/org/tmatesoft/hg/test/TestStatus.java @ 109:dd4d2d0e42cd

Handler for StatusCommand to get notifications in the form of HgStatus object
author Artem Tikhomirov <tikhomirov.artem@gmail.com>
date Sat, 29 Jan 2011 04:17:13 +0100
parents 0b2dcca7de9f
children 2e395db595e2
line wrap: on
line source
/*
 * Copyright (c) 2011 TMate Software Ltd
 *  
 * This program is free software; you can redistribute it and/or modify
 * it under the terms of the GNU General Public License as published by
 * the Free Software Foundation; version 2 of the License.
 *
 * This program is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU General Public License for more details.
 *
 * For information on how to redistribute this software under
 * the terms of a license other than GNU General Public License
 * contact TMate Software at support@hg4j.com
 */
package org.tmatesoft.hg.test;

import static org.hamcrest.CoreMatchers.equalTo;
import static org.tmatesoft.hg.core.StatusCommand.HgStatus.Kind.*;
import static org.tmatesoft.hg.repo.HgRepository.TIP;

import java.util.Collection;
import java.util.Collections;
import java.util.HashMap;
import java.util.LinkedList;
import java.util.List;
import java.util.Map;
import java.util.TreeMap;

import org.junit.Assume;
import org.junit.Rule;
import org.junit.Test;
import org.tmatesoft.hg.core.Path;
import org.tmatesoft.hg.core.StatusCommand;
import org.tmatesoft.hg.core.StatusCommand.HgStatus;
import org.tmatesoft.hg.core.StatusCommand.HgStatus.Kind;
import org.tmatesoft.hg.repo.HgLookup;
import org.tmatesoft.hg.repo.HgRepository;
import org.tmatesoft.hg.repo.HgStatusCollector;
import org.tmatesoft.hg.repo.HgWorkingCopyStatusCollector;


/**
 * 
 * @author Artem Tikhomirov
 * @author TMate Software Ltd.
 */
public class TestStatus {

	@Rule
	public ErrorCollectorExt errorCollector = new ErrorCollectorExt();

	private final HgRepository repo;
	private StatusOutputParser statusParser;
	private ExecHelper eh;

	public static void main(String[] args) throws Throwable {
		TestStatus test = new TestStatus();
		test.testLowLevel();
		test.testStatusCommand();
		test.testPerformance();
		test.errorCollector.verify();
	}
	
	public TestStatus() throws Exception {
		this(new HgLookup().detectFromWorkingDir());
	}

	private TestStatus(HgRepository hgRepo) {
		repo = hgRepo;
		Assume.assumeTrue(!repo.isInvalid());
		statusParser = new StatusOutputParser();
		eh = new ExecHelper(statusParser, null);
	}
	
	@Test
	public void testLowLevel() throws Exception {
		final HgWorkingCopyStatusCollector wcc = new HgWorkingCopyStatusCollector(repo);
		statusParser.reset();
		eh.run("hg", "status", "-A");
		HgStatusCollector.Record r = wcc.status(HgRepository.TIP);
		report("hg status -A", r, statusParser);
		//
		statusParser.reset();
		int revision = 3;
		eh.run("hg", "status", "-A", "--rev", String.valueOf(revision));
		r = wcc.status(revision);
		report("status -A --rev " + revision, r, statusParser);
		//
		statusParser.reset();
		eh.run("hg", "status", "-A", "--change", String.valueOf(revision));
		r = new HgStatusCollector.Record();
		new HgStatusCollector(repo).change(revision, r);
		report("status -A --change " + revision, r, statusParser);
		//
		statusParser.reset();
		int rev2 = 80;
		final String range = String.valueOf(revision) + ":" + String.valueOf(rev2);
		eh.run("hg", "status", "-A", "--rev", range);
		r = new HgStatusCollector(repo).status(revision, rev2);
		report("Status -A -rev " + range, r, statusParser);
	}
	
	@Test
	public void testStatusCommand() throws Exception {
		final StatusCommand sc = new StatusCommand(repo).all();
		StatusCollector r;
		statusParser.reset();
		eh.run("hg", "status", "-A");
		sc.execute(r = new StatusCollector());
		report("hg status -A", r);
		//
		statusParser.reset();
		int revision = 3;
		eh.run("hg", "status", "-A", "--rev", String.valueOf(revision));
		sc.base(revision).execute(r = new StatusCollector());
		report("status -A --rev " + revision, r);
		//
		statusParser.reset();
		eh.run("hg", "status", "-A", "--change", String.valueOf(revision));
		sc.base(TIP).revision(revision).execute(r = new StatusCollector());
		report("status -A --change " + revision, r);
		
		// TODO check not -A, but defaults()/custom set of modifications 
	}
	
	private static class StatusCollector implements StatusCommand.Handler {
		private final Map<StatusCommand.HgStatus.Kind, List<Path>> map = new TreeMap<StatusCommand.HgStatus.Kind, List<Path>>();

		public void handleStatus(HgStatus s) {
			List<Path> l = map.get(s.getKind());
			if (l == null) {
				l = new LinkedList<Path>();
				map.put(s.getKind(), l);
			}
			l.add(s.getPath());
		}
		
		public List<Path> get(Kind k) {
			List<Path> rv = map.get(k);
			if (rv == null) {
				return Collections.emptyList();
			}
			return rv;
		}
	}
	
	public void testRemovedAgainstNonTip() {
		/*
		 status --rev N when a file added past revision N was removed ((both physically and in dirstate), but not yet committed

		 Reports extra REMOVED file (the one added and removed in between). Shall not
		 */
	}
	
	/*
	 * With warm-up of previous tests, 10 runs, time in milliseconds
	 * 'hg status -A': Native client total 953 (95 per run), Java client 94 (9)
	 * 'hg status -A --rev 3:80': Native client total 1828 (182 per run), Java client 235 (23)
	 * 'hg log --debug', 10 runs: Native client total 1766 (176 per run), Java client 78 (7)
	 */
	public void testPerformance() throws Exception {
		final int runs = 10;
		final long start1 = System.currentTimeMillis();
		for (int i = 0; i < runs; i++) {
			statusParser.reset();
			eh.run("hg", "status", "-A", "--rev", "3:80");
		}
		final long start2 = System.currentTimeMillis();
		for (int i = 0; i < runs; i++) {
			StatusCollector r = new StatusCollector();
			new StatusCommand(repo).all().base(3).revision(80).execute(r);
		}
		final long end = System.currentTimeMillis();
		System.out.printf("'hg status -A --rev 3:80', %d runs:  Native client total %d (%d per run), Java client %d (%d)\n", runs, start2-start1, (start2-start1)/runs, end-start2, (end-start2)/runs);
	}
	
	private void report(String what, StatusCollector r) {
		reportNotEqual(what + "#MODIFIED", r.get(Modified), statusParser.getModified());
		reportNotEqual(what + "#ADDED", r.get(Added), statusParser.getAdded());
		reportNotEqual(what + "#REMOVED", r.get(Removed), statusParser.getRemoved());
		reportNotEqual(what + "#CLEAN", r.get(Clean), statusParser.getClean());
		reportNotEqual(what + "#IGNORED", r.get(Ignored), statusParser.getIgnored());
		reportNotEqual(what + "#MISSING", r.get(Missing), statusParser.getMissing());
		reportNotEqual(what + "#UNKNOWN", r.get(Unknown), statusParser.getUnknown());
		// FIXME test copies
	}

	private void report(String what, HgStatusCollector.Record r, StatusOutputParser statusParser) {
		reportNotEqual(what + "#MODIFIED", r.getModified(), statusParser.getModified());
		reportNotEqual(what + "#ADDED", r.getAdded(), statusParser.getAdded());
		reportNotEqual(what + "#REMOVED", r.getRemoved(), statusParser.getRemoved());
		reportNotEqual(what + "#CLEAN", r.getClean(), statusParser.getClean());
		reportNotEqual(what + "#IGNORED", r.getIgnored(), statusParser.getIgnored());
		reportNotEqual(what + "#MISSING", r.getMissing(), statusParser.getMissing());
		reportNotEqual(what + "#UNKNOWN", r.getUnknown(), statusParser.getUnknown());
		List<Path> copiedKeyDiff = difference(r.getCopied().keySet(), statusParser.getCopied().keySet());
		HashMap<Path, String> copyDiff = new HashMap<Path,String>();
		if (copiedKeyDiff.isEmpty()) {
			for (Path jk : r.getCopied().keySet()) {
				Path jv = r.getCopied().get(jk);
				if (statusParser.getCopied().containsKey(jk)) {
					Path cmdv = statusParser.getCopied().get(jk);
					if (!jv.equals(cmdv)) {
						copyDiff.put(jk, jv + " instead of " + cmdv);
					}
				} else {
					copyDiff.put(jk, "ERRONEOUSLY REPORTED IN JAVA");
				}
			}
		}
		errorCollector.checkThat(what + "#Non-matching 'copied' keys: ", copiedKeyDiff, equalTo(Collections.<Path>emptyList()));
		errorCollector.checkThat(what + "#COPIED", copyDiff, equalTo(Collections.<Path,String>emptyMap()));
	}
	
	private <T> void reportNotEqual(String what, Collection<T> l1, Collection<T> l2) {
		List<T> diff = difference(l1, l2);
		errorCollector.checkThat(what, diff, equalTo(Collections.<T>emptyList()));
	}

	private static <T> List<T> difference(Collection<T> l1, Collection<T> l2) {
		LinkedList<T> result = new LinkedList<T>(l2);
		for (T t : l1) {
			if (l2.contains(t)) {
				result.remove(t);
			} else {
				result.add(t);
			}
		}
		return result;
	}
}