blob: d7df24ea570f3dcf52ecb77bab2b82a150fb1360 [file] [log] [blame]
/*
* Copyright 2000-2014 JetBrains s.r.o.
*
* Licensed 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.
*/
package com.intellij.util.xml;
import com.intellij.openapi.application.ApplicationManager;
import com.intellij.openapi.extensions.Extensions;
import com.intellij.openapi.util.Ref;
import com.intellij.openapi.vfs.LocalFileSystem;
import com.intellij.openapi.vfs.VirtualFile;
import com.intellij.psi.PsiManager;
import com.intellij.psi.util.PsiTreeUtil;
import com.intellij.psi.xml.XmlFile;
import com.intellij.psi.xml.XmlTag;
import com.intellij.semantic.SemService;
import com.intellij.testFramework.PlatformTestUtil;
import com.intellij.testFramework.Timings;
import com.intellij.util.xml.impl.DomFileElementImpl;
import com.intellij.util.xml.impl.DomTestCase;
import com.intellij.util.xml.reflect.DomExtender;
import com.intellij.util.xml.reflect.DomExtenderEP;
import com.intellij.util.xml.reflect.DomExtensionsRegistrar;
import org.jetbrains.annotations.NotNull;
import java.util.List;
import java.util.Random;
import java.util.concurrent.CountDownLatch;
/**
* @author peter
*/
public class DomConcurrencyStressTest extends DomTestCase {
private static final int ITERATIONS = Timings.adjustAccordingToMySpeed(239, true);
public void testInternalDomLocksReadConsistency() throws Throwable {
getDomManager().registerFileDescription(new DomFileDescription(MyElement.class, "a"), myTestRootDisposable);
registerExtender(MyElement.class, MyExtender.class);
final XmlFile file = createXmlFile("<a attr=\"1\" attr2=\"2\">" +
"<foo-child><foo-child/><custom-foo/></foo-child>" +
"<bar-child><foo-child/></bar-child>" +
"<foo-element>" +
" <foo-child/>" +
" <foo-child><foo-child attr=\"\"/></foo-child>" +
" <custom-foo/>" +
" <custom-bar/>" +
"</foo-element>" +
"<custom-bar/>" +
"<custom-bar>" +
" <foo-child/>" +
" <foo-element/>" +
" <foo-element/>" +
" <custom-bar/>" +
"</custom-bar>" +
"<custom-bar attr=\"\">" +
" <foo-child/>" +
" <some-child/>" +
" <custom-bar/>" +
"</custom-bar>" +
"<child/>" +
"<child/>" +
"<bool/>" +
"</a>");
System.out.println("ITERATIONS =" + ITERATIONS);
runThreads(42, new Runnable() {
@Override
public void run() {
for (int i = 0; i < ITERATIONS; i++) {
ApplicationManager.getApplication().runReadAction(new Runnable() {
@Override
public void run() {
final DomFileElementImpl<DomElement> element = getDomManager().getFileElement(file);
assertNotNull(element);
element.getRootElement().accept(new DomElementVisitor() {
@Override
public void visitDomElement(final DomElement element) {
if (DomUtil.hasXml(element)) {
element.acceptChildren(this);
}
}
});
}
});
SemService.getSemService(getProject()).clearCache();
}
}
});
}
private void registerExtender(final Class elementClass, final Class extenderClass) {
final DomExtenderEP extenderEP = new DomExtenderEP();
extenderEP.domClassName = elementClass.getName();
extenderEP.extenderClassName = extenderClass.getName();
PlatformTestUtil.registerExtension(Extensions.getRootArea(), DomExtenderEP.EP_NAME, extenderEP, myTestRootDisposable);
}
private static void runThreads(int threadCount, final Runnable runnable) throws Throwable {
for (int i=0; i<threadCount/8 + 1; i++) {
final Ref<Throwable> exc = Ref.create(null);
final CountDownLatch reads = new CountDownLatch(8);
for (int j = 0; j < 8; j++) {
new Thread(){
@Override
public void run() {
try {
runnable.run();
}
catch (Throwable e) {
exc.set(e);
}
finally {
reads.countDown();
}
}
}.start();
}
reads.await();
if (!exc.isNull()) {
throw exc.get();
}
}
}
public interface MyElement extends DomElement {
MyElement getFooChild();
MyElement getBarChild();
MyElement getSomeChild();
List<MyElement> getFooElements();
MyElement addFooElement();
List<MyElement> getChildren();
MyElement addChild();
GenericAttributeValue<String> getAttr();
GenericAttributeValue<String> getAttr2();
@SubTag(indicator = true)
GenericDomValue<Boolean> getBool();
}
public static class MyExtender extends DomExtender<MyElement> {
private final Random myRandom = new Random();
@Override
public void registerExtensions(@NotNull MyElement myElement, @NotNull DomExtensionsRegistrar registrar) {
for (MyElement element : myElement.getFooElements()) {
element.getFooElements();
}
if (myRandom.nextInt(20) < 2) {
try {
Thread.sleep(1);
}
catch (InterruptedException ignored) {
}
}
registrar.registerFixedNumberChildExtension(new XmlName("custom-foo", null), MyElement.class);
myElement.getSomeChild().getFooElements();
myElement.getFooChild().getFooChild().getAttr();
myElement.getAttr();
registrar.registerCollectionChildrenExtension(new XmlName("custom-bar", null), MyElement.class);
}
}
public void testBigCustomFile() throws Throwable {
getDomManager().registerFileDescription(new DomFileDescription(MyAllCustomElement.class, "component"), myTestRootDisposable);
registerExtender(MyAllCustomElement.class, MyAllCustomExtender.class);
VirtualFile bigXml = LocalFileSystem.getInstance().findFileByPath(
PlatformTestUtil.getCommunityPath() + "/xml/dom-tests/testData/performance.xml");
assert bigXml != null;
final XmlFile file = (XmlFile)PsiManager.getInstance(ourProject).findFile(bigXml);
runThreads(42, new Runnable() {
@Override
public void run() {
final Random random = new Random();
for (int i = 0; i < ITERATIONS; i++) {
ApplicationManager.getApplication().runReadAction(new Runnable() {
@Override
public void run() {
int offset = random.nextInt(file.getTextLength() - 10);
XmlTag tag = PsiTreeUtil.findElementOfClassAtOffset(file, offset, XmlTag.class, false);
assert tag != null : offset;
DomElement element = DomUtil.getDomElement(tag);
assert element instanceof MyAllCustomElement : element;
}
});
if (random.nextInt(50) == 0) {
SemService.getSemService(getProject()).clearCache();
}
}
}
});
}
public interface MyAllCustomElement extends DomElement {}
public static class MyAllCustomExtender extends DomExtender<MyAllCustomElement> {
@Override
public void registerExtensions(@NotNull MyAllCustomElement element, @NotNull DomExtensionsRegistrar registrar) {
try {
DomElement parent = element;
while (parent != null) {
for (DomElement child : DomUtil.getDefinedChildren(parent, true, true)) {
DomElement childParent = child.getParent();
if (!parent.equals(childParent)) {
String parentText = parent.getXmlTag().getText();
String childText = child.getXmlElement().getText();
child.getParent();
parent.equals(childParent);
throw new AssertionError(parent.getXmlElement() + "; " + childParent + "; " + child.getXmlElement().getText());
}
}
parent = parent.getParent();
}
}
catch (StackOverflowError e) {
System.out.println(Thread.currentThread() + ": " + element.getXmlTag().getName() + element.getXmlTag().hashCode());
throw e;
}
registrar.registerCustomChildrenExtension(MyAllCustomElement.class);
}
}
}