Return-Path: X-Original-To: archive-asf-public-internal@cust-asf2.ponee.io Delivered-To: archive-asf-public-internal@cust-asf2.ponee.io Received: from cust-asf.ponee.io (cust-asf.ponee.io [163.172.22.183]) by cust-asf2.ponee.io (Postfix) with ESMTP id 326E0200BB1 for ; Wed, 19 Oct 2016 15:10:08 +0200 (CEST) Received: by cust-asf.ponee.io (Postfix) id 3139E160ADE; Wed, 19 Oct 2016 13:10:08 +0000 (UTC) Delivered-To: archive-asf-public@cust-asf.ponee.io Received: from mail.apache.org (hermes.apache.org [140.211.11.3]) by cust-asf.ponee.io (Postfix) with SMTP id 51C1F160AFC for ; Wed, 19 Oct 2016 15:10:07 +0200 (CEST) Received: (qmail 89288 invoked by uid 500); 19 Oct 2016 13:10:06 -0000 Mailing-List: contact commits-help@jena.apache.org; run by ezmlm Precedence: bulk List-Help: List-Unsubscribe: List-Post: List-Id: Reply-To: dev@jena.apache.org Delivered-To: mailing list commits@jena.apache.org Received: (qmail 89168 invoked by uid 99); 19 Oct 2016 13:10:05 -0000 Received: from git1-us-west.apache.org (HELO git1-us-west.apache.org) (140.211.11.23) by apache.org (qpsmtpd/0.29) with ESMTP; Wed, 19 Oct 2016 13:10:05 +0000 Received: by git1-us-west.apache.org (ASF Mail Server at git1-us-west.apache.org, from userid 33) id 61F5BE00DC; Wed, 19 Oct 2016 13:10:05 +0000 (UTC) Content-Type: text/plain; charset="us-ascii" MIME-Version: 1.0 Content-Transfer-Encoding: 7bit From: andy@apache.org To: commits@jena.apache.org Date: Wed, 19 Oct 2016 13:10:05 -0000 Message-Id: <0460d6bf00694dbcbd9b2feffa801ee3@git.apache.org> X-Mailer: ASF-Git Admin Mailer Subject: [1/6] jena git commit: ServerCtl ported from Fuseki2. archived-at: Wed, 19 Oct 2016 13:10:08 -0000 Repository: jena Updated Branches: refs/heads/master e5d9db53e -> 60302eeee ServerCtl ported from Fuseki2. Project: http://git-wip-us.apache.org/repos/asf/jena/repo Commit: http://git-wip-us.apache.org/repos/asf/jena/commit/015b871e Tree: http://git-wip-us.apache.org/repos/asf/jena/tree/015b871e Diff: http://git-wip-us.apache.org/repos/asf/jena/diff/015b871e Branch: refs/heads/master Commit: 015b871e80afb0348414d4bea9e29e396b31d05f Parents: fd30b2f Author: Andy Seaborne Authored: Tue Oct 18 22:02:08 2016 +0100 Committer: Andy Seaborne Committed: Tue Oct 18 22:09:52 2016 +0100 ---------------------------------------------------------------------- .../jena/fuseki/embedded/FusekiTestServer.java | 241 +++++++++++++++++++ 1 file changed, 241 insertions(+) ---------------------------------------------------------------------- http://git-wip-us.apache.org/repos/asf/jena/blob/015b871e/jena-fuseki2/jena-fuseki-embedded/src/test/java/org/apache/jena/fuseki/embedded/FusekiTestServer.java ---------------------------------------------------------------------- diff --git a/jena-fuseki2/jena-fuseki-embedded/src/test/java/org/apache/jena/fuseki/embedded/FusekiTestServer.java b/jena-fuseki2/jena-fuseki-embedded/src/test/java/org/apache/jena/fuseki/embedded/FusekiTestServer.java new file mode 100644 index 0000000..351010e --- /dev/null +++ b/jena-fuseki2/jena-fuseki-embedded/src/test/java/org/apache/jena/fuseki/embedded/FusekiTestServer.java @@ -0,0 +1,241 @@ +/** + * 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. + */ + +package org.apache.jena.fuseki.embedded; + +import static org.apache.jena.fuseki.embedded.FusekiTestServer.ServerScope.CLASS ; +import static org.apache.jena.fuseki.embedded.FusekiTestServer.ServerScope.SUITE ; +import static org.apache.jena.fuseki.embedded.FusekiTestServer.ServerScope.TEST ; + +import java.io.IOException ; +import java.net.ServerSocket ; +import java.util.concurrent.atomic.AtomicInteger ; + +import org.apache.http.client.HttpClient ; +import org.apache.http.impl.client.CloseableHttpClient ; +import org.apache.jena.atlas.io.IO ; +import org.apache.jena.fuseki.FusekiException ; +import org.apache.jena.riot.web.HttpOp ; +import org.apache.jena.sparql.core.DatasetGraph ; +import org.apache.jena.sparql.core.DatasetGraphFactory ; +import org.apache.jena.sparql.modify.request.Target ; +import org.apache.jena.sparql.modify.request.UpdateDrop ; +import org.apache.jena.system.Txn ; +import org.apache.jena.update.Update ; +import org.apache.jena.update.UpdateExecutionFactory ; +import org.apache.jena.update.UpdateProcessor ; + +// NOT FINISHED + +/** + * Manage a single server for use with tests. It supports three modes: + *
    + *
  • {@code ServerScope.SUITE} : One server for a whole test suite + *
  • {@code ServerScope.CLASS} : One server per test class + *
  • {@code ServerScope.TEST} :One server per individual test + *
+ * One server per individual test can be troublesome due to connections not closing down + * fast enough and can also be slow. + *

One server per test class is a good compromise. + *

The data in the server is always reset between tests in all modes. + *

+ * Using a connection pooling HttpClient (see {@link HttpOp#createPoolingHttpClient()}) is important, + * both for test performance and for reducing the TCP connection load on the operating system. + *

+ * Usage: + *

+ *

+ * In the test suite, put: + * + *

+ *  {@literal @BeforeClass} static public void beforeSuiteClass() { ServerCtl.ctlBeforeTestSuite(); } 
+ *  {@literal @AfterClass}  static public void afterSuiteClass()  { ServerCtl.ctlAfterTestSuite(); }
+ * 
+ *

+ * In the test class, put: + *

+ * {@literal @BeforeClass} public static void ctlBeforeClass() { ServerCtl.ctlBeforeClass(); }
+ * {@literal @AfterClass}  public static void ctlAfterClass()  { ServerCtl.ctlAfterClass(); }
+ * {@literal @Before}      public void ctlBeforeTest()         { ServerCtl.ctlBeforeTest(); }
+ * {@literal @After}       public void ctlAfterTest()          { ServerCtl.ctlAfterTest(); }
+ * 
+ */ +public class FusekiTestServer { + /* Cut&Paste versions: + + Test suite (TS_*) + @BeforeClass static public void beforeSuiteClass() { ServerCtl.ctlBeforeTestSuite(); } + @AfterClass static public void afterSuiteClass() { ServerCtl.ctlAfterTestSuite(); } + + Test class (Test*) + @BeforeClass public static void ctlBeforeClass() { ServerCtl.ctlBeforeClass(); } + @AfterClass public static void ctlAfterClass() { ServerCtl.ctlAfterClass(); } + @Before public void ctlBeforeTest() { ServerCtl.ctlBeforeTest(); } + @After public void ctlAfterTest() { ServerCtl.ctlAfterTest(); } + */ + + static HttpClient defaultHttpClient = HttpOp.getDefaultHttpClient(); + + // Note: it is important to cleanly close a PoolingHttpClient across server restarts + // otherwise the pooled connections remain for the old server. + + /*package : for import static */ enum ServerScope { SUITE, CLASS, TEST } + private static ServerScope serverScope = ServerScope.CLASS ; + private static int currentPort = choosePort() ; + + public static int port() { + return currentPort ; + } + + // Whether to use a transaction on the dataset or to use SPARQL Update. + static boolean CLEAR_DSG_DIRECTLY = true ; + static private DatasetGraph dsgTesting ; + + // reference count of start/stop server + private static AtomicInteger countServer = new AtomicInteger() ; + private static FusekiEmbeddedServer server = null ; + + public static final String urlRoot() { return "http://localhost:"+port()+"/" ; } + public static final String datasetPath() { return "/ds_test" ; } + public static final String urlDataset() { return "http://localhost:"+port()+datasetPath() ; } + + public static final String serviceUpdate() { return "http://localhost:"+port()+datasetPath()+"/update" ; } + public static final String serviceQuery() { return "http://localhost:"+port()+datasetPath()+"/query" ; } + public static final String serviceGSP() { return "http://localhost:"+port()+datasetPath()+"/data" ; } + + public static void ctlBeforeTestSuite() { + if ( serverScope == SUITE ) { + setPoolingHttpClient() ; + allocServer(); + } + } + + public static void ctlAfterTestSuite() { + if ( serverScope == SUITE ) { + freeServer(); + resetDefaultHttpClient() ; + } + } + + /** + * Setup for the tests by allocating a Fuseki instance to work with + */ + public static void ctlBeforeClass() { + if ( serverScope == CLASS ) { + setPoolingHttpClient() ; + allocServer(); + } + } + + /** + * Clean up after tests by de-allocating the Fuseki instance + */ + public static void ctlAfterClass() { + if ( serverScope == CLASS ) { + freeServer(); + resetDefaultHttpClient() ; + } + } + + /** + * Placeholder. + */ + public static void ctlBeforeTest() { + if ( serverScope == TEST ) { + setPoolingHttpClient() ; + allocServer(); + } + } + + /** + * Clean up after each test by resetting the Fuseki dataset + */ + public static void ctlAfterTest() { + if ( serverScope == TEST ) { + freeServer(); + resetDefaultHttpClient() ; + } else + resetServer(); + } + + /** Set a PoolingHttpClient */ + private static void setPoolingHttpClient() { + setHttpClient(HttpOp.createPoolingHttpClient()) ; + } + + /** Restore the original setup */ + private static void resetDefaultHttpClient() { + setHttpClient(defaultHttpClient); + } + + /** Set the HttpClient - close the old one if appropriate */ + /*package*/ static void setHttpClient(HttpClient newHttpClient) { + HttpClient hc = HttpOp.getDefaultHttpClient() ; + if ( hc instanceof CloseableHttpClient ) + IO.close((CloseableHttpClient)hc) ; + HttpOp.setDefaultHttpClient(newHttpClient) ; + } + + /*package*/ static void allocServer() { + if ( countServer.getAndIncrement() == 0 ) + setupServer(true) ; + } + + /*package*/ static void freeServer() { + if ( countServer.decrementAndGet() == 0 ) + teardownServer() ; + } + + /*package*/ static void setupServer(boolean updateable) { + dsgTesting = DatasetGraphFactory.createTxnMem() ; + server = + FusekiEmbeddedServer.create() + .setPort(port()) + .setLoopback(true) + .add(datasetPath(), dsgTesting) + .build(); + } + + /*package*/ static void teardownServer() { + if ( server != null ) + server.stop() ; + server = null ; + } + + /*package*/ static void resetServer() { + if (countServer.get() == 0) + throw new RuntimeException("No server started!"); + if ( CLEAR_DSG_DIRECTLY ) { + Txn.executeWrite(dsgTesting, ()->dsgTesting.clear()) ; + } else { + Update clearRequest = new UpdateDrop(Target.ALL) ; + UpdateProcessor proc = UpdateExecutionFactory.createRemote(clearRequest, serviceUpdate()) ; + try {proc.execute() ; } + catch (Throwable e) {e.printStackTrace(); throw e;} + } + } + + /** Choose an unused port for a server to listen on */ + private static int choosePort() { + try (ServerSocket s = new ServerSocket(0)) { + return s.getLocalPort(); + } catch (IOException ex) { + throw new FusekiException("Failed to find a port for tests!"); + } + } +}