/*******************************************************************************
 *  Copyright (c) 2008, 2017 IBM Corporation and others.
 *
 *  This program and the accompanying materials
 *  are made available under the terms of the Eclipse Public License 2.0
 *  which accompanies this distribution, and is available at
 *  https://www.eclipse.org/legal/epl-2.0/
 *
 *  SPDX-License-Identifier: EPL-2.0
 *
 *  Contributors:
 *     IBM Corporation - initial API and implementation
 *******************************************************************************/
package org.eclipse.equinox.p2.tests.updatesite;

import java.io.ByteArrayOutputStream;
import java.io.File;
import java.io.PrintStream;
import java.lang.reflect.Field;
import java.net.URI;
import java.net.URISyntaxException;
import java.util.Collection;
import java.util.Map;
import javax.xml.parsers.DocumentBuilder;
import javax.xml.parsers.DocumentBuilderFactory;
import junit.framework.Test;
import junit.framework.TestSuite;
import org.eclipse.core.runtime.IProgressMonitor;
import org.eclipse.core.runtime.IStatus;
import org.eclipse.core.runtime.NullProgressMonitor;
import org.eclipse.core.runtime.URIUtil;
import org.eclipse.equinox.internal.p2.artifact.repository.MirrorSelector;
import org.eclipse.equinox.internal.p2.artifact.repository.RawMirrorRequest;
import org.eclipse.equinox.internal.p2.artifact.repository.simple.SimpleArtifactRepository;
import org.eclipse.equinox.internal.p2.metadata.ArtifactKey;
import org.eclipse.equinox.internal.p2.metadata.IRequiredCapability;
import org.eclipse.equinox.internal.p2.updatesite.SiteFeature;
import org.eclipse.equinox.internal.p2.updatesite.UpdateSite;
import org.eclipse.equinox.internal.p2.updatesite.artifact.UpdateSiteArtifactRepository;
import org.eclipse.equinox.p2.core.ProvisionException;
import org.eclipse.equinox.p2.metadata.IArtifactKey;
import org.eclipse.equinox.p2.metadata.IInstallableUnit;
import org.eclipse.equinox.p2.metadata.IRequirement;
import org.eclipse.equinox.p2.metadata.ITouchpointData;
import org.eclipse.equinox.p2.metadata.ITouchpointInstruction;
import org.eclipse.equinox.p2.metadata.Version;
import org.eclipse.equinox.p2.metadata.VersionRange;
import org.eclipse.equinox.p2.query.IQuery;
import org.eclipse.equinox.p2.query.IQueryResult;
import org.eclipse.equinox.p2.query.QueryUtil;
import org.eclipse.equinox.p2.repository.IRepository;
import org.eclipse.equinox.p2.repository.IRepositoryManager;
import org.eclipse.equinox.p2.repository.artifact.ArtifactKeyQuery;
import org.eclipse.equinox.p2.repository.artifact.IArtifactDescriptor;
import org.eclipse.equinox.p2.repository.artifact.IArtifactRepository;
import org.eclipse.equinox.p2.repository.artifact.IArtifactRepositoryManager;
import org.eclipse.equinox.p2.repository.artifact.spi.ArtifactDescriptor;
import org.eclipse.equinox.p2.repository.metadata.IMetadataRepository;
import org.eclipse.equinox.p2.repository.metadata.IMetadataRepositoryManager;
import org.eclipse.equinox.p2.repository.spi.AbstractRepository;
import org.eclipse.equinox.p2.tests.AbstractProvisioningTest;
import org.eclipse.equinox.p2.tests.StringBufferStream;
import org.w3c.dom.Document;
import org.w3c.dom.Element;
import org.w3c.dom.NodeList;

/**
 * @since 1.0
 */
public class UpdateSiteTest extends AbstractProvisioningTest {
	/*
	 * Constructor for the class.
	 */
	public UpdateSiteTest(String name) {
		super(name);
	}

	/*
	 * Run all the tests in this class.
	 */
	public static Test suite() {
		return new TestSuite(UpdateSiteTest.class);
	}

	public void testRelativeSiteURL() {
		File site = getTestData("0.1", "/testData/updatesite/siteurl");
		UpdateSite updatesite = null;
		try {
			updatesite = UpdateSite.load(site.toURI(), getTransport(), getMonitor());
		} catch (ProvisionException e) {
			fail("0.2", e);
		}

		try {
			int featureCount = updatesite.loadFeatures(new NullProgressMonitor()).length;
			assertEquals(1, featureCount);
		} catch (ProvisionException e) {
			fail("0.4", e);
		}
	}

	public void testAbsoluteSiteURL() {
		File site = getTestData("0.1", "/testData/updatesite/siteurl2");
		File siteDirectory = getTestData("0.1", "/testData/updatesite/siteurl2/siteurl/");
		UpdateSite updatesite = null;
		try {
			updatesite = UpdateSite.load(site.toURI(), getTransport(), getMonitor());
			updatesite.getSite().setLocationURIString(siteDirectory.toURI().toString());
		} catch (ProvisionException e) {
			fail("0.2", e);
		}

		try {
			int featureCount = updatesite.loadFeatures(new NullProgressMonitor()).length;
			assertEquals(1, featureCount);
		} catch (ProvisionException e) {
			fail("0.4", e);
		}
	}

	public void testDefaultDigestURL() {
		File site = getTestData("0.1", "/testData/updatesite/digest");
		UpdateSite updatesite = null;
		try {
			updatesite = UpdateSite.load(site.toURI(), getTransport(), getMonitor());
		} catch (ProvisionException e) {
			fail("0.2", e);
		}

		try {
			int featureCount = updatesite.loadFeatures(new NullProgressMonitor()).length;
			assertEquals(1, featureCount);
		} catch (ProvisionException e) {
			fail("0.4", e);
		}
	}

	public void testZippedDefaultDigestURL() throws URISyntaxException {
		File site = getTestData("0.1", "/testData/updatesite/digest/site.zip");
		URI siteURI = new URI("jar:" + site.toURI() + "!/");
		UpdateSite updatesite = null;
		try {
			updatesite = UpdateSite.load(siteURI, getTransport(), getMonitor());
		} catch (ProvisionException e) {
			fail("0.2", e);
		}

		try {
			int featureCount = updatesite.loadFeatures(new NullProgressMonitor()).length;
			assertEquals(1, featureCount);
		} catch (ProvisionException e) {
			fail("0.4", e);
		}
	}

	public void testRelativeDigestURL() {
		File site = getTestData("0.1", "/testData/updatesite/digesturl");
		UpdateSite updatesite = null;
		try {
			updatesite = UpdateSite.load(site.toURI(), getTransport(), getMonitor());
		} catch (ProvisionException e) {
			fail("0.2", e);
		}

		try {
			int featureCount = updatesite.loadFeatures(new NullProgressMonitor()).length;
			assertEquals(1, featureCount);
		} catch (ProvisionException e) {
			fail("0.4", e);
		}
	}

	public void testAbsoluteDigestURL() {
		File site = getTestData("0.1", "/testData/updatesite/digesturl2");
		File digestDirectory = getTestData("0.1", "/testData/updatesite/digesturl2/digesturl/");
		UpdateSite updatesite = null;
		try {
			updatesite = UpdateSite.load(site.toURI(), getTransport(), getMonitor());
			updatesite.getSite().setDigestURIString(digestDirectory.toURI().toString());
		} catch (ProvisionException e) {
			fail("0.2", e);
		}

		try {
			int featureCount = updatesite.loadFeatures(new NullProgressMonitor()).length;
			assertEquals(1, featureCount);
		} catch (ProvisionException e) {
			fail("0.4", e);
		}
	}

	/*
	 * Test in which we load an update site from a valid site.xml file. Handle
	 * all the variations in the file.
	 */
	public void testNoDigestGoodSite() {
		File site = getTestData("0.1", "/testData/updatesite/site");
		UpdateSite updatesite = null;
		try {
			updatesite = UpdateSite.load(site.toURI(), getTransport(), getMonitor());
		} catch (ProvisionException e) {
			fail("0.2", e);
		}
		try {
			int featureCount = updatesite.loadFeatures(new NullProgressMonitor()).length;
			assertEquals(1, featureCount);
		} catch (ProvisionException e) {
			fail("0.4", e);
		}
	}

	public void testNoEndingSlashURL() {
		File base = getTestData("0.1", "/testData/updatesite");
		UpdateSite updatesite = null;
		try {
			URI siteURL = base.toURI().resolve("site");
			updatesite = UpdateSite.load(siteURL, getTransport(), getMonitor());
		} catch (ProvisionException e) {
			fail("0.2", e);
		}
		try {
			int featureCount = updatesite.loadFeatures(new NullProgressMonitor()).length;
			assertEquals(1, featureCount);
		} catch (ProvisionException e) {
			fail("0.4", e);
		}
	}

	public void testSiteXMLURL() {
		File site = getTestData("0.1", "/testData/updatesite/site/site.xml");
		UpdateSite updatesite = null;
		try {
			updatesite = UpdateSite.load(site.toURI(), getTransport(), getMonitor());
		} catch (ProvisionException e) {
			fail("0.2", e);
		}
		try {
			int featureCount = updatesite.loadFeatures(getMonitor()).length;
			assertEquals(1, featureCount);
		} catch (ProvisionException e) {
			fail("0.4", e);
		}
	}

	public void testSiteWithSpaces() {
		File site = getTestData("0.1", "/testData/updatesite/site with spaces/");
		UpdateSite updatesite = null;
		try {
			updatesite = UpdateSite.load(site.toURI(), getTransport(), getMonitor());
		} catch (ProvisionException e) {
			fail("0.2", e);
		}
		try {
			int featureCount = updatesite.loadFeatures(new NullProgressMonitor()).length;
			assertEquals(1, featureCount);
		} catch (ProvisionException e) {
			fail("0.4", e);
		}
	}

	public void testXXXSiteXXXXMLURL() {
		File site = getTestData("0.1", "/testData/updatesite/xxxsitexxx/xxxsitexxx.xml");
		UpdateSite updatesite = null;
		try {
			updatesite = UpdateSite.load(site.toURI(), getTransport(), getMonitor());
		} catch (ProvisionException e) {
			fail("0.2", e);
		}
		try {
			int featureCount = updatesite.loadFeatures(new NullProgressMonitor()).length;
			assertEquals(1, featureCount);
		} catch (ProvisionException e) {
			fail("0.4", e);
		}
	}

	public void testBadXXXSiteXXXXMLURL() {
		File siteDir = getTestData("0.1", "/testData/updatesite/xxxsitexxx");
		File site = new File(siteDir, "site.xml");
		try {
			UpdateSite.load(site.toURI(), getTransport(), getMonitor());
			fail("0.2");
		} catch (ProvisionException e) {
			// expected
		}
	}

	public void testBadDigestGoodSite() {
		File site = getTestData("0.1", "/testData/updatesite/baddigestgoodsite");
		UpdateSite updatesite = null;
		try {
			updatesite = UpdateSite.load(site.toURI(), getTransport(), getMonitor());
		} catch (ProvisionException e) {
			fail("0.2", e);
		}
		try {
			updatesite.loadFeatures(new NullProgressMonitor());
		} catch (ProvisionException e) {
			fail("0.4", e);
		}
	}

	public void testCorruptDigestGoodSite() {
		File site = getTestData("0.1", "/testData/updatesite/corruptdigestgoodsite");
		UpdateSite updatesite = null;
		try {
			updatesite = UpdateSite.load(site.toURI(), getTransport(), getMonitor());
		} catch (ProvisionException e) {
			fail("0.2", e);
		}
		StringBuffer buffer = new StringBuffer();
		PrintStream out = System.out;
		try {
			System.setOut(new PrintStream(new StringBufferStream(buffer)));
			updatesite.loadFeatures(new NullProgressMonitor());
		} catch (ProvisionException e) {
			fail("0.4", e);
		} finally {
			System.setOut(out);
		}
		assertTrue(buffer.toString().contains("Content is not allowed in prolog."));
	}

	public void testBadDigestBadSite() {
		File site = getTestData("0.1", "/testData/updatesite/baddigestbadsite");
		try {
			UpdateSite.load(site.toURI(), getTransport(), getMonitor());
			fail("0.2");
		} catch (ProvisionException e) {
			// expected
		}
	}

	public void testBadSiteXML() {
		// handle the case where the site.xml doesn't parse correctly
		File site = getTestData("0.1", "/testData/updatesite/badSiteXML");
		try {
			UpdateSite.load(site.toURI(), getTransport(), getMonitor());
			fail("0.2");
		} catch (ProvisionException e) {
			// expected exception
		}
	}

	/*
	 * Test the case where we don't have a digest or site.xml.
	 */
	public void testNoSite() {
		// ensure we have a validate, empty location
		File temp = getTempFolder();
		temp.mkdirs();
		try {
			UpdateSite.load(temp.toURI(), getTransport(), getMonitor());
			fail("0.2");
		} catch (ProvisionException e) {
			// we expect an exception
		}
	}

	public void testNullSite() {
		try {
			assertNull("1.0", UpdateSite.load(null, getTransport(), getMonitor()));
		} catch (ProvisionException e) {
			fail("1.99", e);
		}
	}

	public void testBadFeatureURL() {
		File site = getTestData("0.1", "/testData/updatesite/badfeatureurl");
		UpdateSite updatesite = null;
		try {
			updatesite = UpdateSite.load(site.toURI(), getTransport(), getMonitor());
		} catch (ProvisionException e) {
			fail("0.2", e);
		}
		StringBuffer buffer = new StringBuffer();
		PrintStream out = System.out;
		try {
			System.setOut(new PrintStream(new StringBufferStream(buffer)));
			int featureCount = updatesite.loadFeatures(new NullProgressMonitor()).length;
			assertEquals(0, featureCount);
		} catch (ProvisionException e) {
			fail("0.5");

		} finally {
			System.setOut(out);
		}
		assertTrue(buffer.toString().contains("Error reading feature"));
	}

	public void testGoodFeatureURL() {
		File site = getTestData("0.1", "/testData/updatesite/goodfeatureurl");
		UpdateSite updatesite = null;
		try {
			updatesite = UpdateSite.load(site.toURI(), getTransport(), getMonitor());
		} catch (ProvisionException e) {
			fail("0.2", e);
		}
		try {
			int featureCount = updatesite.loadFeatures(new NullProgressMonitor()).length;
			assertEquals(1, featureCount);
		} catch (ProvisionException e) {
			fail("0.5");
		}
	}

	public void testZippedGoodFeatureURL() throws URISyntaxException {

		File site = getTestData("0.1", "/testData/updatesite/goodfeatureurl/site.zip");
		URI siteURI = new URI("jar:" + site.toURI() + "!/");
		UpdateSite updatesite = null;
		try {
			updatesite = UpdateSite.load(siteURI, getTransport(), getMonitor());
		} catch (ProvisionException e) {
			fail("0.2", e);
		}

		try {
			int featureCount = updatesite.loadFeatures(new NullProgressMonitor()).length;
			assertEquals(1, featureCount);
		} catch (ProvisionException e) {
			fail("0.5");
		}
	}

	public void testIncludedFeature() {
		File site = getTestData("0.1", "/testData/updatesite/includedfeature");
		UpdateSite updatesite = null;
		try {
			updatesite = UpdateSite.load(site.toURI(), getTransport(), getMonitor());
		} catch (ProvisionException e) {
			fail("0.2", e);
		}
		try {
			int featureCount = updatesite.loadFeatures(new NullProgressMonitor()).length;
			assertEquals(2, featureCount);
		} catch (ProvisionException e) {
			fail("0.5");
		}
	}

	public void testIncludedFeatureArchive() {
		File site = getTestData("0.1", "/testData/updatesite/includedfeaturearchive");
		UpdateSite updatesite = null;
		try {
			updatesite = UpdateSite.load(site.toURI(), getTransport(), getMonitor());
		} catch (ProvisionException e) {
			fail("0.2", e);
		}
		try {
			int featureCount = updatesite.loadFeatures(new NullProgressMonitor()).length;
			assertEquals(2, featureCount);
		} catch (ProvisionException e) {
			fail("0.5");
		}
	}

	public void testBadIncludedFeatureArchive() {
		File site = getTestData("0.1", "/testData/updatesite/badincludedfeaturearchive");
		UpdateSite updatesite = null;
		try {
			updatesite = UpdateSite.load(site.toURI(), getTransport(), getMonitor());
		} catch (ProvisionException e) {
			fail("0.2", e);
		}
		StringBuffer buffer = new StringBuffer();
		PrintStream out = System.out;
		try {
			System.setOut(new PrintStream(new StringBufferStream(buffer)));
			int featureCount = updatesite.loadFeatures(new NullProgressMonitor()).length;
			assertEquals(1, featureCount);
		} catch (ProvisionException e) {
			fail("0.5");
		} finally {
			System.setOut(out);
		}
		assertTrue(buffer.toString().contains("Error reading feature"));
	}

	public void testNoFeatureIdAndVersion() {
		File site = getTestData("0.1", "/testData/updatesite/nofeatureidandversion");
		UpdateSite updatesite = null;
		try {
			updatesite = UpdateSite.load(site.toURI(), getTransport(), getMonitor());
		} catch (ProvisionException e) {
			fail("0.2", e);
		}
		try {
			int featureCount = updatesite.loadFeatures(new NullProgressMonitor()).length;
			assertEquals(2, featureCount);
		} catch (ProvisionException e) {
			fail("0.5");
		}
	}

	public void testSiteFeatureVersionEquals() {
		SiteFeature a = new SiteFeature();
		SiteFeature b = new SiteFeature();
		assertEquals("1.0", a, b);
		b.setFeatureVersion("1.0.0");
		a.setFeatureVersion("1.0.0");
		b.setFeatureVersion("1.0.0");
		assertEquals("1.1", a, b);
		b.setFeatureVersion("2.0.0");
		assertFalse("1.2", a.equals(b));
		b.setFeatureVersion(null);
		assertFalse("1.3", a.equals(b));
		assertFalse("1.4", b.equals(a));
	}

	public void testSiteFeatureLabelEquals() {
		SiteFeature a = new SiteFeature();
		SiteFeature b = new SiteFeature();
		assertEquals("1.0", a, b);
		a.setLabel("foo");
		b.setLabel("foo");
		assertEquals("1.1", a, b);
		b.setLabel("bar");
		assertFalse("1.2", a.equals(b));
		b.setLabel(null);
		assertFalse("1.3", a.equals(b));
		assertFalse("1.4", b.equals(a));
	}

	public void testSiteFeatureIDEquals() {
		SiteFeature a = new SiteFeature();
		SiteFeature b = new SiteFeature();
		assertEquals("1.0", a, b);
		a.setFeatureIdentifier("org.foo");
		b.setFeatureIdentifier("org.foo");
		assertEquals("1.1", a, b);
		b.setFeatureIdentifier("org.bar");
		assertFalse("1.2", a.equals(b));
		b.setFeatureIdentifier(null);
		assertFalse("1.3", a.equals(b));
		assertFalse("1.4", b.equals(a));
	}

	public void testSiteFeatureEquals() {
		SiteFeature a = new SiteFeature();
		SiteFeature b = new SiteFeature();
		assertEquals("1.0", a, b);
		a.setURLString("http://foo");
		assertFalse("1.1", a.equals(b));
		b.setURLString("http://foo");
		assertEquals("1.2", a, b);
		a.setURLString("http://FOO");
		assertEquals("1.3", a, b);
		a.setURLString("file://FOO");
		assertFalse("1.4", a.equals(b));
		a.setURLString(null);
		assertFalse("1.5", a.equals(b));
		assertFalse("1.6", b.equals(a));
	}

	public void testSiteFeatureHash() {
		SiteFeature a = new SiteFeature();
		SiteFeature b = new SiteFeature();
		assertEquals("1.0", a.hashCode(), b.hashCode());
		a.setURLString("http://foo");
		b.setURLString("http://foo");
		assertEquals("1.1", a.hashCode(), b.hashCode());
		a.setURLString("http://FOO/");
		assertEquals("1.2", a.hashCode(), b.hashCode());
		a.setURLString("foo");
		b.setURLString("FoO");
		assertEquals("1.3", a.hashCode(), b.hashCode());
	}

	public void testSiteFeatureNotEquals() {
		SiteFeature a = new SiteFeature();
		SiteFeature b = new SiteFeature();
		assertEquals("1.0", a, b);
		a.setURLString("file:/c:/foo");
		assertFalse("1.1", a.equals(b));
		b.setURLString("file:/c:/bar");
		assertFalse("1.2", a.equals(b));
		assertFalse("1.3", b.equals(a));
		a.setURLString("http://foo");
		b.setURLString("http://bar/");
		assertFalse("1.4", b.equals(a));
	}

	public void testSiteFeatureFileURL() {
		SiteFeature a = new SiteFeature();
		SiteFeature b = new SiteFeature();
		assertEquals("1.0", a, b);
		a.setURLString("file:/c:/foo");
		b.setURLString("file:/c:/FOO");
		if (a.equals(b))
			assertEquals("1.1", a.hashCode(), b.hashCode());
		a.setURLString("FILE:/c:/foo");
		b.setURLString("file:/c:/FOO");
		if (a.equals(b))
			assertEquals("1.2", a.hashCode(), b.hashCode());
		a.setURLString("HTTP://example.com");
		b.setURLString("HTtP://example.com");
		if (a.equals(b))
			assertEquals("1.3", a.hashCode(), b.hashCode());
		a.setURLString("HTTP://eXaMpLe.com");
		b.setURLString("HTtP://example.com");
		if (a.equals(b))
			assertEquals("1.4", a.hashCode(), b.hashCode());
		a.setURLString("HTTP://eXaMpLe.com/");
		b.setURLString("HTtP://example.com");
		assertEquals(a, b);
		if (a.equals(b))
			assertEquals("1.5", a.hashCode(), b.hashCode());
		a.setURLString("http://localhost");
		b.setURLString("http://127.0.0.1");
		if (a.equals(b))
			assertEquals("1.6", a.hashCode(), b.hashCode());
	}

	public void testRepoWithFeatureWithNullUpdateURL() {
		IMetadataRepositoryManager repoMan = getAgent().getService(IMetadataRepositoryManager.class);
		assertNotNull(repoMan);
		File site = getTestData("Update site", "/testData/updatesite/missingUpdateURLFeature/");
		IMetadataRepository metadataRepo = null;
		StringBuffer buffer = new StringBuffer();
		PrintStream out = System.out;
		try {
			System.setOut(new PrintStream(new StringBufferStream(buffer)));
			metadataRepo = repoMan.loadRepository(site.toURI(), null);
		} catch (ProvisionException e) {
			fail("Can't load repository missingUpdateURLFeature");
		} finally {
			System.setOut(out);
		}
		assertTrue(buffer.toString().contains("Invalid site reference null in feature test.featurewithmissingupdateurl."));
		IQuery<IInstallableUnit> query = QueryUtil.createIUQuery("test.featurewithmissingupdateurl.feature.group", Version.create("1.0.0"));
		IQueryResult<IInstallableUnit> result = metadataRepo.query(query, null);
		assertEquals("1.0", 1, queryResultSize(result));
	}

	/**
	 * Tests that a feature requiring a bundle with no range is converted correctly.
	 */
	public void testBug243422() {
		IMetadataRepositoryManager repoMan = getAgent().getService(IMetadataRepositoryManager.class);
		assertNotNull(repoMan);
		File site = getTestData("Update site", "/testData/updatesite/UpdateSite243422/");
		IMetadataRepository metadataRepo = null;
		try {
			metadataRepo = repoMan.loadRepository(site.toURI(), null);
		} catch (ProvisionException e) {
			fail("Can't load repository UpdateSite243422");
		}
		IQuery<IInstallableUnit> query = QueryUtil.createIUQuery("org.eclipse.jdt.astview.feature.feature.group", Version.create("1.0.1"));
		IQueryResult<IInstallableUnit> result = metadataRepo.query(query, null);
		assertEquals("1.0", 1, queryResultSize(result));
		IInstallableUnit featureIU = result.iterator().next();
		for (IRequirement requirement : featureIU.getRequirements()) {
			IRequiredCapability req = (IRequiredCapability) requirement;
			if (req.getName().equals("org.eclipse.ui.ide")) {
				assertEquals("2.0", VersionRange.emptyRange, req.getRange());
			}
		}
	}

	public void testShortenVersionNumberInFeature() {
		IArtifactRepositoryManager repoMan = getAgent().getService(IArtifactRepositoryManager.class);
		assertNotNull(repoMan);
		File site = getTestData("Update site", "/testData/updatesite/240121/UpdateSite240121/");
		IArtifactRepository artifactRepo = null;
		try {
			artifactRepo = repoMan.loadRepository(site.toURI(), null);
		} catch (ProvisionException e) {
			fail("Can't load repository UpdateSite240121");
		}
		IQueryResult<IArtifactKey> keys = artifactRepo.query(new ArtifactKeyQuery(null, "Plugin240121", null), null);
		assertEquals(1, queryResultSize(keys));
		IArtifactKey key = keys.iterator().next();
		IStatus status = artifactRepo.getArtifact(artifactRepo.getArtifactDescriptors(key)[0], new ByteArrayOutputStream(500), new NullProgressMonitor());
		if (!status.isOK())
			fail("Can't get the expected artifact:" + key);
	}

	/**
	 * Tests that the feature jar IU has the appropriate touchpoint instruction for
	 * unzipping the feature on install.
	 */
	public void testFeatureJarUnzipInstruction() {
		IMetadataRepositoryManager repoMan = getAgent().getService(IMetadataRepositoryManager.class);
		File site = getTestData("0.1", "/testData/updatesite/site");
		URI location = null;
		location = site.toURI();
		IMetadataRepository repository;
		try {
			repository = repoMan.loadRepository(location, getMonitor());
		} catch (ProvisionException e) {
			fail("1.99", e);
			return;
		}
		IQueryResult<IInstallableUnit> result = repository.query(QueryUtil.createIUQuery("test.feature.feature.jar"), getMonitor());
		assertTrue("1.0", !result.isEmpty());
		IInstallableUnit unit = result.iterator().next();
		Collection<ITouchpointData> data = unit.getTouchpointData();
		assertEquals("1.1", 1, data.size());
		Map<String, ITouchpointInstruction> instructions = data.iterator().next().getInstructions();
		assertEquals("1.2", 1, instructions.size());
		assertEquals("1.3", "true", instructions.get("zipped").getBody());
	}

	/**
	 * TODO Failing test, see bug 265528.
	 */
	public void _testFeatureSiteReferences() throws ProvisionException, URISyntaxException {
		File site = getTestData("0.1", "/testData/updatesite/siteFeatureReferences");
		URI siteURI = site.toURI();
		URI testUpdateSite = new URI("https://download.eclipse.org/test/updatesite/");
		URI testDiscoverySite = new URI("https://download.eclipse.org/test/discoverysite");

		IMetadataRepositoryManager manager = getAgent().getService(IMetadataRepositoryManager.class);
		assertNotNull(manager);
		manager.removeRepository(testUpdateSite);
		manager.removeRepository(testDiscoverySite);
		IMetadataRepository repository = manager.loadRepository(siteURI, 0, getMonitor());
		try {
			//wait for site references to be published asynchronously
			Thread.sleep(1000);
		} catch (InterruptedException e) {
			fail("4.99", e);
		}
		assertNotNull(repository);
		assertTrue("1.0", manager.contains(testUpdateSite));
		assertTrue("1.1", manager.contains(testDiscoverySite));
		assertFalse("1.2", manager.isEnabled(testUpdateSite));
		assertFalse("1.3", manager.isEnabled(testDiscoverySite));
	}

	public void testMetadataRepoCount() {
		File site = getTestData("0.1", "/testData/updatesite/site");
		URI siteURI = site.toURI();

		IMetadataRepositoryManager metadataRepoMan = getAgent().getService(IMetadataRepositoryManager.class);
		assertNotNull(metadataRepoMan);

		URI[] knownRepos = metadataRepoMan.getKnownRepositories(IRepositoryManager.REPOSITORIES_ALL);
		for (URI knownRepo : knownRepos) {
			if (siteURI.equals(knownRepo)) {
				metadataRepoMan.removeRepository(siteURI);
				knownRepos = metadataRepoMan.getKnownRepositories(IRepositoryManager.REPOSITORIES_ALL);
				break;
			}
		}

		try {
			metadataRepoMan.loadRepository(site.toURI(), getMonitor());
		} catch (ProvisionException e) {
			fail("1.0", e);
			return;
		}
		URI[] afterKnownRepos = metadataRepoMan.getKnownRepositories(IRepositoryManager.REPOSITORIES_ALL);
		assertTrue("1.1", afterKnownRepos.length == knownRepos.length + 1);
	}

	public void testArtifactRepoCount() {
		File site = getTestData("0.1", "/testData/updatesite/site");
		URI siteURI = site.toURI();

		IArtifactRepositoryManager artifactRepoMan = getAgent().getService(IArtifactRepositoryManager.class);
		assertNotNull(artifactRepoMan);

		URI[] knownRepos = artifactRepoMan.getKnownRepositories(IRepositoryManager.REPOSITORIES_ALL);
		for (URI knownRepo : knownRepos) {
			if (siteURI.equals(knownRepo)) {
				artifactRepoMan.removeRepository(siteURI);
				knownRepos = artifactRepoMan.getKnownRepositories(IRepositoryManager.REPOSITORIES_ALL);
				break;
			}
		}

		try {
			artifactRepoMan.loadRepository(site.toURI(), getMonitor());
		} catch (ProvisionException e) {
			fail("1.0", e);
			return;
		}
		URI[] afterKnownRepos = artifactRepoMan.getKnownRepositories(IRepositoryManager.REPOSITORIES_ALL);
		assertTrue("1.1", afterKnownRepos.length == knownRepos.length + 1);
	}


	@SuppressWarnings("removal")
	public void testMirrors() {
		String testDataLocation = "/testData/updatesite/packedSiteWithMirror";
		File targetLocation = null;
		URI siteURI = getTestData("0.1", testDataLocation).toURI();
		try {
			IArtifactKey key = new ArtifactKey("osgi.bundle", "test.fragment", Version.create("1.0.0"));
			// Load source repository
			IArtifactRepository sourceRepo = getArtifactRepositoryManager().loadRepository(siteURI, getMonitor());

			// Hijack source repository's mirror selector
			new OrderedMirrorSelector(sourceRepo, testDataLocation);

			// Create target repository
			targetLocation = File.createTempFile("target", ".repo");
			targetLocation.delete();
			targetLocation.mkdirs();
			IArtifactRepository targetRepository = new SimpleArtifactRepository(getAgent(), "TargetRepo", targetLocation.toURI(), null);

			// Load the packed descriptor
			IArtifactDescriptor[] descriptors = sourceRepo.getArtifactDescriptors(key);
			IArtifactDescriptor descriptor = null;
			for (int i = 0; i < descriptors.length && descriptor == null; i++)
				if (!IArtifactDescriptor.FORMAT_PACKED.equals(descriptors[i].getProperty(IArtifactDescriptor.FORMAT)))
					descriptor = descriptors[i];

			if (descriptor == null)
				fail("0.3");

			RawMirrorRequest mirror = new RawMirrorRequest(descriptor, new ArtifactDescriptor(descriptor), targetRepository, getTransport());
			mirror.perform(sourceRepo, getMonitor());

			assertTrue(mirror.getResult().isOK());
			assertTrue(targetRepository.contains(key));
		} catch (Exception e) {
			fail("0.2", e);
		} finally {
			if (targetLocation != null)
				delete(targetLocation);
			getArtifactRepositoryManager().removeRepository(siteURI);
		}
	}

	/*
	 * Special mirror selector for testing which chooses mirrors in order
	 */
	protected class OrderedMirrorSelector extends MirrorSelector {
		private URI repoLocation;
		int index = 0;
		MirrorInfo[] mirrors;
		IArtifactRepository repo;

		OrderedMirrorSelector(IArtifactRepository repo, String testDataLocation) throws Exception {
			super(repo, getTransport());
			this.repo = repo;
			// Alternatively we could use reflect to change "location" of the repo
			setRepoSelector();
			getRepoLocation();
			mirrors = computeMirrors("file:///" + getTestData("Mirror Location", testDataLocation + '/' + repo.getProperties().get(IRepository.PROP_MIRRORS_URL)).toString().replace('\\', '/'));
		}

		private void setRepoSelector() throws Exception {
			Field delegate = UpdateSiteArtifactRepository.class.getDeclaredField("delegate");
			delegate.setAccessible(true);
			// Hijack the source repository's MirrorSelector with ours which provides mirrors in order.
			Field mirrorsField = SimpleArtifactRepository.class.getDeclaredField("mirrors");
			mirrorsField.setAccessible(true);
			mirrorsField.set(delegate.get(repo), this);

			// Setting this property forces SimpleArtifactRepository to use mirrors despite being a local repo
			Field properties = AbstractRepository.class.getDeclaredField("properties");
			properties.setAccessible(true);
			((Map) properties.get(delegate.get(repo))).put(SimpleArtifactRepository.PROP_FORCE_THREADING, String.valueOf(true));
		}

		// Overridden to prevent mirror sorting
		@Override
		public synchronized void reportResult(String toDownload, IStatus result) {
			return;
		}

		// We want to test each mirror once.
		@Override
		public synchronized boolean hasValidMirror() {
			return mirrors != null && index < mirrors.length;
		}

		@Override
		public synchronized URI getMirrorLocation(URI inputLocation, IProgressMonitor monitor) {
			return URIUtil.append(nextMirror(), repoLocation.relativize(inputLocation).getPath());
		}

		private URI nextMirror() {
			Field mirrorLocation = null;
			try {
				mirrorLocation = MirrorInfo.class.getDeclaredField("locationString");
				mirrorLocation.setAccessible(true);

				if (index < mirrors.length)
					return URIUtil.makeAbsolute(new URI((String) mirrorLocation.get(mirrors[index++])), repoLocation);
				return repoLocation;
			} catch (Exception e) {
				fail(Double.toString(0.4 + index), e);
				return null;
			}
		}

		private synchronized void getRepoLocation() {
			Field locationField = null;
			try {
				locationField = UpdateSiteArtifactRepository.class.getDeclaredField("location");
				locationField.setAccessible(true);
				repoLocation = (URI) locationField.get(repo);
			} catch (Exception e) {
				fail("0.3", e);
			}
		}

		private MirrorInfo[] computeMirrors(String mirrorsURL) {
			// Copied & modified from MirrorSelector
			try {
				DocumentBuilderFactory domFactory = DocumentBuilderFactory.newInstance();
				DocumentBuilder builder = domFactory.newDocumentBuilder();
				Document document = builder.parse(mirrorsURL);
				if (document == null)
					return null;
				NodeList mirrorNodes = document.getElementsByTagName("mirror"); //$NON-NLS-1$
				int mirrorCount = mirrorNodes.getLength();
				MirrorInfo[] infos = new MirrorInfo[mirrorCount + 1];
				for (int i = 0; i < mirrorCount; i++) {
					Element mirrorNode = (Element) mirrorNodes.item(i);
					String infoURL = mirrorNode.getAttribute("url"); //$NON-NLS-1$
					infos[i] = new MirrorInfo(infoURL, i);
				}
				//p2: add the base site as the last resort mirror so we can track download speed and failure rate
				infos[mirrorCount] = new MirrorInfo(repoLocation.toString(), mirrorCount);
				return infos;
			} catch (Exception e) {
				// log if absolute url
				if (mirrorsURL != null && (mirrorsURL.startsWith("http://") //$NON-NLS-1$
						|| mirrorsURL.startsWith("https://") //$NON-NLS-1$
						|| mirrorsURL.startsWith("file://") //$NON-NLS-1$
						|| mirrorsURL.startsWith("ftp://") //$NON-NLS-1$
						|| mirrorsURL.startsWith("jar://"))) //$NON-NLS-1$
					fail("Error processing mirrors URL: " + mirrorsURL, e); //$NON-NLS-1$
				return null;
			}
		}
	}
}