blob: b778272012f12a40abe275dc3db7ad32f7d1b77b [file] [log] [blame]
// Lucene version compatibility level 4.8.1
using J2N.Threading;
using J2N.Threading.Atomic;
using NUnit.Framework;
using System;
using System.Globalization;
using System.IO;
using Assert = Lucene.Net.TestFramework.Assert;
using JCG = J2N.Collections.Generic;
namespace Lucene.Net.Facet.Taxonomy.Directory
{
using Directory = Lucene.Net.Store.Directory;
using DiskOrdinalMap = Lucene.Net.Facet.Taxonomy.Directory.DirectoryTaxonomyWriter.DiskOrdinalMap;
using IOrdinalMap = Lucene.Net.Facet.Taxonomy.Directory.DirectoryTaxonomyWriter.IOrdinalMap;
using IOUtils = Lucene.Net.Util.IOUtils;
using MemoryOrdinalMap = Lucene.Net.Facet.Taxonomy.Directory.DirectoryTaxonomyWriter.MemoryOrdinalMap;
using TestUtil = Lucene.Net.Util.TestUtil;
/*
* Licensed to the Apache Software Foundation (ASF) under one or more
* contributor license agreements. See the NOTICE file distributed with
* this work for additional information regarding copyright ownership.
* The ASF licenses this file to You under the Apache License, Version 2.0
* (the "License"); you may not use this file except in compliance with
* the License. You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
[TestFixture]
public class TestAddTaxonomy : FacetTestCase
{
private void Dotest(int ncats, int range)
{
AtomicInt32 numCats = new AtomicInt32(ncats);
Directory[] dirs = new Directory[2];
for (int i = 0; i < dirs.Length; i++)
{
dirs[i] = NewDirectory();
var tw = new DirectoryTaxonomyWriter(dirs[i]);
ThreadJob[] addThreads = new ThreadJob[4];
for (int j = 0; j < addThreads.Length; j++)
{
addThreads[j] = new ThreadAnonymousInnerClassHelper(this, range, numCats, tw);
}
foreach (ThreadJob t in addThreads)
{
t.Start();
}
foreach (ThreadJob t in addThreads)
{
t.Join();
}
tw.Dispose();
}
var tw1 = new DirectoryTaxonomyWriter(dirs[0]);
IOrdinalMap map = randomOrdinalMap();
tw1.AddTaxonomy(dirs[1], map);
tw1.Dispose();
validate(dirs[0], dirs[1], map);
IOUtils.Dispose(dirs);
}
private class ThreadAnonymousInnerClassHelper : ThreadJob
{
private readonly TestAddTaxonomy outerInstance;
private int range;
private AtomicInt32 numCats;
private DirectoryTaxonomyWriter tw;
public ThreadAnonymousInnerClassHelper(TestAddTaxonomy outerInstance, int range, AtomicInt32 numCats, DirectoryTaxonomyWriter tw)
{
this.outerInstance = outerInstance;
this.range = range;
this.numCats = numCats;
this.tw = tw;
}
public override void Run()
{
Random random = Random;
while (numCats.DecrementAndGet() > 0)
{
string cat = Convert.ToString(random.Next(range), CultureInfo.InvariantCulture);
try
{
tw.AddCategory(new FacetLabel("a", cat));
}
catch (IOException e)
{
throw new Exception(e.ToString(), e);
}
}
}
}
private IOrdinalMap randomOrdinalMap()
{
if (Random.NextBoolean())
{
return new DiskOrdinalMap(CreateTempFile("taxoMap", "").FullName);
}
else
{
return new MemoryOrdinalMap();
}
}
private void validate(Directory dest, Directory src, IOrdinalMap ordMap)
{
using var destTr = new DirectoryTaxonomyReader(dest);
int destSize = destTr.Count;
using var srcTR = new DirectoryTaxonomyReader(src);
var map = ordMap.GetMap();
// validate taxo sizes
int srcSize = srcTR.Count;
Assert.IsTrue(destSize >= srcSize, "destination taxonomy expected to be larger than source; dest=" + destSize + " src=" + srcSize);
// validate that all source categories exist in destination, and their
// ordinals are as expected.
for (int j = 1; j < srcSize; j++)
{
FacetLabel cp = srcTR.GetPath(j);
int destOrdinal = destTr.GetOrdinal(cp);
Assert.IsTrue(destOrdinal > 0, cp + " not found in destination");
Assert.AreEqual(destOrdinal, map[j]);
}
}
[Test]
public virtual void TestAddEmpty()
{
Directory dest = NewDirectory();
var destTW = new DirectoryTaxonomyWriter(dest);
destTW.AddCategory(new FacetLabel("Author", "Rob Pike"));
destTW.AddCategory(new FacetLabel("Aardvarks", "Bob"));
destTW.Commit();
Directory src = NewDirectory();
new DirectoryTaxonomyWriter(src).Dispose(); // create an empty taxonomy
IOrdinalMap map = randomOrdinalMap();
destTW.AddTaxonomy(src, map);
destTW.Dispose();
validate(dest, src, map);
IOUtils.Dispose(dest, src);
}
[Test]
public virtual void TestAddToEmpty()
{
Directory dest = NewDirectory();
Directory src = NewDirectory();
DirectoryTaxonomyWriter srcTW = new DirectoryTaxonomyWriter(src);
srcTW.AddCategory(new FacetLabel("Author", "Rob Pike"));
srcTW.AddCategory(new FacetLabel("Aardvarks", "Bob"));
srcTW.Dispose();
DirectoryTaxonomyWriter destTW = new DirectoryTaxonomyWriter(dest);
IOrdinalMap map = randomOrdinalMap();
destTW.AddTaxonomy(src, map);
destTW.Dispose();
validate(dest, src, map);
IOUtils.Dispose(dest, src);
}
// A more comprehensive and big random test.
[Test]
[Slow]
public virtual void TestBig()
{
Dotest(200, 10000);
Dotest(1000, 20000);
Dotest(400000, 1000000);
}
// a reasonable random test
[Test]
public virtual void TestMedium()
{
Random random = Random;
int numTests = AtLeast(3);
for (int i = 0; i < numTests; i++)
{
Dotest(TestUtil.NextInt32(random, 2, 100),
TestUtil.NextInt32(random, 100, 1000));
}
}
[Test]
public virtual void TestSimple()
{
Directory dest = NewDirectory();
var tw1 = new DirectoryTaxonomyWriter(dest);
tw1.AddCategory(new FacetLabel("Author", "Mark Twain"));
tw1.AddCategory(new FacetLabel("Animals", "Dog"));
tw1.AddCategory(new FacetLabel("Author", "Rob Pike"));
Directory src = NewDirectory();
var tw2 = new DirectoryTaxonomyWriter(src);
tw2.AddCategory(new FacetLabel("Author", "Rob Pike"));
tw2.AddCategory(new FacetLabel("Aardvarks", "Bob"));
tw2.Dispose();
IOrdinalMap map = randomOrdinalMap();
tw1.AddTaxonomy(src, map);
tw1.Dispose();
validate(dest, src, map);
IOUtils.Dispose(dest, src);
}
[Test]
public virtual void TestConcurrency()
{
// tests that addTaxonomy and addCategory work in parallel
int numCategories = AtLeast(10000);
// build an input taxonomy index
Directory src = NewDirectory();
var tw = new DirectoryTaxonomyWriter(src);
for (int i = 0; i < numCategories; i++)
{
tw.AddCategory(new FacetLabel("a", Convert.ToString(i, CultureInfo.InvariantCulture)));
}
tw.Dispose();
// now add the taxonomy to an empty taxonomy, while adding the categories
// again, in parallel -- in the end, no duplicate categories should exist.
Directory dest = NewDirectory();
var destTw = new DirectoryTaxonomyWriter(dest);
var t = new ThreadAnonymousInnerClassHelper2(this, numCategories, destTw);
t.Start();
IOrdinalMap map = new MemoryOrdinalMap();
destTw.AddTaxonomy(src, map);
t.Join();
destTw.Dispose();
// now validate
var dtr = new DirectoryTaxonomyReader(dest);
// +2 to account for the root category + "a"
Assert.AreEqual(numCategories + 2, dtr.Count);
var categories = new JCG.HashSet<FacetLabel>();
for (int i = 1; i < dtr.Count; i++)
{
FacetLabel cat = dtr.GetPath(i);
Assert.IsTrue(categories.Add(cat), "category " + cat + " already existed");
}
dtr.Dispose();
IOUtils.Dispose(src, dest);
}
private class ThreadAnonymousInnerClassHelper2 : ThreadJob
{
private readonly TestAddTaxonomy outerInstance;
private readonly int numCategories;
private readonly DirectoryTaxonomyWriter destTW;
public ThreadAnonymousInnerClassHelper2(TestAddTaxonomy outerInstance, int numCategories, DirectoryTaxonomyWriter destTW)
{
this.outerInstance = outerInstance;
this.numCategories = numCategories;
this.destTW = destTW;
}
public override void Run()
{
for (int i = 0; i < numCategories; i++)
{
try
{
destTW.AddCategory(new FacetLabel("a", Convert.ToString(i, CultureInfo.InvariantCulture)));
}
catch (IOException e)
{
// shouldn't happen - if it does, let the test fail on uncaught exception.
throw new Exception(e.ToString(), e);
}
}
}
}
}
}