/* * Copyright 2007 Xu, Chuan <xuchuan@gmail.com> * * This file is part of ZOJ. * * ZOJ 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; either revision 3 of the License, or (at your option) any later revision. * * ZOJ 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. * * You should have received a copy of the GNU General Public License along with ZOJ. if not, see * <http://www.gnu.org/licenses/>. */ package cn.edu.zju.acm.onlinejudge.judgeservice; import static org.hamcrest.core.IsNull.*; import static org.hamcrest.core.Is.*; import static org.junit.Assert.*; import java.util.ArrayList; import java.util.HashMap; import java.util.List; import java.util.Map; import org.junit.Before; import org.junit.Test; import cn.edu.zju.acm.onlinejudge.bean.Submission; public class JudgingQueueUnitTest { private JudgingQueue queue; private Submission[] submissions; @Before public void setUp() { this.queue = new JudgingQueue(); this.submissions = new Submission[10]; for (int i = 0; i < this.submissions.length; ++i) { this.submissions[i] = new Submission(); this.submissions[i].setId(i); } } @Test public void testEmptyQueue() { JudgingQueueIterator iter = this.queue.iterator(); assertThat(iter.next(), nullValue()); } @Test public void testPush() { this.queue.push(this.submissions[0]); JudgingQueueIterator iter = this.queue.iterator(); assertThat(iter.next(), is(this.submissions[0])); assertThat(iter.next(), nullValue()); } @Test public void testIteratorBeforePush() { JudgingQueueIterator iter = this.queue.iterator(); this.queue.push(this.submissions[0]); assertThat(iter.next(), is(this.submissions[0])); assertThat(iter.next(), nullValue()); } @Test public void testMultiplePush() { this.queue.push(this.submissions[0]); this.queue.push(this.submissions[1]); JudgingQueueIterator iter = this.queue.iterator(); assertThat(iter.next(), is(this.submissions[0])); this.queue.push(this.submissions[2]); assertThat(iter.next(), is(this.submissions[1])); assertThat(iter.next(), is(this.submissions[2])); this.queue.push(this.submissions[3]); assertThat(iter.next(), is(this.submissions[3])); assertThat(iter.next(), nullValue()); } @Test public void testRemoveBeforeIterator() { this.queue.push(this.submissions[0]); this.queue.remove(this.submissions[0]); JudgingQueueIterator iter = this.queue.iterator(); assertThat(iter.next(), nullValue()); } @Test public void testRemoveAfterIterator() { this.queue.push(this.submissions[0]); JudgingQueueIterator iter = this.queue.iterator(); this.queue.remove(this.submissions[0]); assertThat(iter.next(), is(this.submissions[0])); assertThat(iter.next(), nullValue()); } @Test public void testRemoveNotExisting() { this.queue.push(this.submissions[0]); this.queue.remove(this.submissions[1]); JudgingQueueIterator iter = this.queue.iterator(); assertThat(iter.next(), is(this.submissions[0])); assertThat(iter.next(), nullValue()); } @Test public void testMultipleIterator() { this.queue.push(this.submissions[0]); this.queue.push(this.submissions[1]); JudgingQueueIterator iter = this.queue.iterator(); assertThat(iter.next(), is(this.submissions[0])); this.queue.push(this.submissions[2]); assertThat(iter.next(), is(this.submissions[1])); assertThat(iter.next(), is(this.submissions[2])); assertThat(iter.next(), nullValue()); iter = this.queue.iterator(); assertThat(iter.next(), is(this.submissions[0])); assertThat(iter.next(), is(this.submissions[1])); assertThat(iter.next(), is(this.submissions[2])); assertThat(iter.next(), nullValue()); } @Test public void testMix() { this.queue.push(this.submissions[0]); this.queue.push(this.submissions[1]); JudgingQueueIterator iter1 = this.queue.iterator(); assertThat(iter1.next(), is(this.submissions[0])); this.queue.remove(this.submissions[1]); JudgingQueueIterator iter2 = this.queue.iterator(); this.queue.push(this.submissions[2]); assertThat(iter1.next(), is(this.submissions[1])); assertThat(iter2.next(), is(this.submissions[0])); assertThat(iter2.next(), is(this.submissions[1])); this.queue.remove(this.submissions[0]); JudgingQueueIterator iter3 = this.queue.iterator(); assertThat(iter1.next(), is(this.submissions[2])); assertThat(iter2.next(), is(this.submissions[2])); assertThat(iter3.next(), is(this.submissions[2])); this.queue.push(this.submissions[3]); assertThat(iter1.next(), is(this.submissions[3])); assertThat(iter2.next(), is(this.submissions[3])); assertThat(iter3.next(), is(this.submissions[3])); assertThat(iter1.next(), nullValue()); assertThat(iter2.next(), nullValue()); assertThat(iter3.next(), nullValue()); } /** * This test works in this way: We first save a global iterator, then create a set of judge threads which add and * remove submissions and another set of check threads which get iterators from the queue. We finally assert that * every submission sequence seen by a check thread is a sub-sequence of that returned by the global iterator saved * before. In order to be efficient, we do not save all submission sequence seen by check threads. Instead, we save the * hash code of submission ids, the first submission id and total number of submissions in the sequence. */ @Test public void testMultipleThreads() { JudgingQueueIterator allIter = queue.iterator(); final Thread[] judge = new Thread[50]; final int maxIdPerJudgeThread = 1000; for (int i = 0; i < judge.length; ++i) { final int id = i; judge[i] = new Thread() { public void run() { for (int i = 0; i < maxIdPerJudgeThread; ++i) { Submission submission = new Submission(); submission.setId(id * maxIdPerJudgeThread + i); queue.push(submission); Thread.yield(); queue.remove(submission); } } }; judge[i].start(); } Thread[] check = new Thread[100]; final long[] start = new long[check.length]; final int[] len = new int[check.length]; final long[] hash = new long[check.length]; for (int i = 0; i < check.length; ++i) { final int id = i; check[i] = new Thread() { public void run() { hash[id] = len[id] = 0; JudgingQueueIterator iter = queue.iterator(); for (int i = 0; i < 100; ++i) { Submission submission = iter.next(); if (submission != null) { start[id] = hash[id] = submission.getId(); break; } Thread.yield(); } if (len[id] > 0) { try { Thread.sleep(1000); } catch (InterruptedException e) {} for (Submission submission = iter.next(); submission != null; submission = iter.next()) { ++len[id]; hash[id] = hash[id] * 31 + submission.getId(); } } } }; check[i].start(); } for (int i = 0; i < judge.length; ++i) { try { judge[i].join(); } catch (InterruptedException e) {} } for (int i = 0; i < check.length; ++i) { try { check[i].join(); } catch (InterruptedException e) {} } List<Long> h = new ArrayList<Long>(); List<Long> base = new ArrayList<Long>(); Map<Long, Integer> m = new HashMap<Long, Integer>(); long[] last = new long[judge.length]; for (int i = 0; i < last.length; ++i) { last[i] = -1; } h.add(0L); base.add(1L); for (Submission submission = allIter.next(); submission != null; submission = allIter.next()) { long id = submission.getId(); m.put(id, h.size()); h.add(h.get(h.size() - 1) * 31 + id); base.add(base.get(base.size() - 1) * 31); int a = (int) (id / maxIdPerJudgeThread); long b = id % maxIdPerJudgeThread; if (last[a] < 0) { last[a] = b; } else { assertThat(b, is(last[a] + 1)); last[a] = b; } } for (int i = 0; i < check.length; ++i) { if (len[i] > 0) { int s = m.get(start[i]); assertThat(hash[i], is(h.get(s + len[i] - 1) - h.get(s - 1) * base.get(len[i]))); } } } }