ServiceTest2.java [src/csip/test] Revision: 9f8d9f2ecc2cd2580651ae87c22c9a2a4dfcac15  Date: Fri Apr 08 13:28:49 MDT 2016
/*
 * $Id$
 * 
 * This file is part of the Cloud Services Integration Platform (CSIP),
 * 2010-2013, Olaf David and others, Colorado State University.
 *
 * CSIP is free software: you can redistribute it and/or modify
 * it under the terms of the GNU Lesser General Public License as published by
 * the Free Software Foundation, version 2.1.
 *
 * CSIP 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 Lesser General Public License for more details.
 *
 * You should have received a copy of the GNU Lesser General Public License
 * along with OMS.  If not, see <http://www.gnu.org/licenses/lgpl.txt>.
 */
package csip.test;

import csip.Client;
import csip.ModelDataService;
import csip.utils.JSONUtils;
import csip.utils.Services;
import java.io.File;
import java.net.URISyntaxException;
import java.util.Arrays;
import java.util.Date;
import java.util.List;
import java.util.Map;
import java.util.Properties;
import java.util.concurrent.Callable;
import java.util.logging.Level;
import java.util.logging.Logger;
import java.util.logging.SimpleFormatter;
import java.util.logging.StreamHandler;
import org.apache.commons.io.FileUtils;
import org.apache.http.client.utils.URIBuilder;
import org.codehaus.jettison.json.JSONArray;
import org.codehaus.jettison.json.JSONException;
import org.codehaus.jettison.json.JSONObject;
import org.skyscreamer.jsonassert.JSONAssert;

/**
 * Service Testing.
 *
 * @author odavid
 */
public class ServiceTest2 {

    static final String KEYSONLY = "keysonly";
    static final String STRICT = "strict";
    static final String KEYVALUE = "keyvalue";
    static final String NONE = "none";

    static final List<String> VALIDTESTING = Arrays.asList(KEYSONLY, KEYVALUE, STRICT, NONE);

    File folder;
    File[] files;
    File target;

    String testing;
    int concurrent = 1;
    int timeout = 1000;
    int delay = 0;
    int rounds = 1;
    String contextUrl = null;

    boolean downloadFiles = true;
    String logLevel = "WARNING";
    final JSONArray reports = new JSONArray();

    static final Logger log = Logger.getLogger("ServiceTest");


    static {
        log.setLevel(Level.SEVERE);
        SimpleFormatter fmt = new SimpleFormatter();
        StreamHandler sh = new StreamHandler(System.out, fmt);
        log.addHandler(sh);
    }


    private ServiceTest2(File target, Properties config) throws Exception {
        if (!target.exists()) {
            throw new IllegalArgumentException("Not a folder or file: " + target);
        }
        this.target = target;
        if (target.getName().endsWith(".json") && target.isFile()) {
            folder = target.getParentFile();
            files = new File[]{target};
        } else if (target.isDirectory()) {
            folder = target;
            files = folder.listFiles((File pathname) -> pathname.getName().endsWith(".json"));
            Arrays.sort(files);
        } else {
            throw new IllegalArgumentException("Invalid argument: " + target);
        }

        // load the service testing settings.
        testing = config.getProperty("verify", NONE);  // strict, keys
        if (!VALIDTESTING.contains(testing)) {
            throw new RuntimeException("invalid testing method: " + testing);
        }

        String d  = config.getProperty("contextURL", System.getProperty("csip.test.contexturl"));
        if (d != null && !d.equals("${csip.test.contexturl}")) {
            contextUrl = d;
        }

         d = config.getProperty("concurrency", System.getProperty("csip.test.concurrency"));
        if (d != null) {
            concurrent = Integer.parseInt(d);
        }

        d = config.getProperty("timeout", System.getProperty("csip.test.timeout"));
        if (d != null) {
            timeout = Integer.parseInt(d);
        }

        d = config.getProperty("delay", System.getProperty("csip.test.delay"));
        if (d != null) {
            delay = Integer.parseInt(d);
        }

        d = config.getProperty("rounds", System.getProperty("csip.test.rounds"));
        if (d != null) {
            rounds = Integer.parseInt(d);
        }

        // warmup phase: 0-off, 1-on
        d = config.getProperty("download_files", System.getProperty("csip.test.downloadfiles"));
        if (d != null) {
            downloadFiles = Boolean.parseBoolean(d);
        }

        logLevel = config.getProperty("logLevel", "WARNING");
        if ((logLevel != null) && (logLevel.length() > 0)) {
            log.setLevel(Level.parse(logLevel));
        }
    }


    /**
     * Execute the test.
     *
     * @return
     */
    JSONObject test() {

        int attempts = 4;
        int bq_size = 4;
        final Stats stats = new Stats();

        for (int i = 0; i < rounds; i++) {
            Services.runParallel(files.length, concurrent, attempts, bq_size, new Services.CallableFactory() {
                @Override
                public Callable create(final int i) {
                    return (Callable) () -> {
                        if (delay > 0) {
                            try {
                                Thread.sleep(delay);
                            } catch (InterruptedException ex) {
                            }
                        }
                        JSONObject report = new JSONObject();
                        JSONObject result = null;
                        try {
                            File f = files[i];
                            JSONObject json = new JSONObject(FileUtils.readFileToString(f));
                            String url = JSONUtils.getMetaInfo(json).getString("service_url");
                            if (contextUrl != null) {
                                url = contextUrl + getServicePath(url);
                            }

                            System.out.println(f.getName() + " >>>>>>>>> " + url + " ");
                            report.put("file", f.toString());
                            report.put("service", url);

                            long p = Client.ping(url, timeout);
                            if (p == -1) {
                                throw new Exception("Url not reachable.");
                            }

                            //  System.out.println(req);
                            // new request
                            JSONObject req = JSONUtils.newRequest();
                            JSONObject req_meta = new JSONObject();

                            if (JSONUtils.getMetaInfo(json).has("request-results")) {
                                JSONArray a = JSONUtils.getMetaInfo(json).getJSONArray("request-results");
                                for (int j = 0; j < a.length(); j++) {
                                    req_meta.accumulate("request-results", a.getString(j));
                                }
                            }

                            req.put("parameter", json.getJSONArray("parameter"));
                            req.put("metainfo", req_meta);

                            System.out.println("REQ>>>>>>>>>>>>>>>>>>>>>> " + req.toString(4));

                            Client client = new Client(timeout, log);
                            JSONObject response = client.doPOST(url, req);

                            System.out.println("RES>>>>>>>>>>>>>>>>>>>>>> " + response.toString(4));

                            if (response.has("metainfo")) {
                                report.put("metainfo", response.getJSONObject("metainfo"));
                            }
                            if (!JSONUtils.hasResult(response)) {
                                throw new Exception("No 'result' in response.");
                            }
                            if (JSONUtils.getStatus(response).equals("Failed")) {
                                JSONObject respose_metainfo = response.getJSONObject("metainfo");
                                throw new Exception("Failed, " + respose_metainfo.getString("error"));
                            }

                            //  if (downloadFiles) {
                            //   download(res, getResFolder(reqFile), client);
                            //  }
                            //
                            result = runtest(json, response);
                            report.put("test", result);
                            stats.incSuccess();
                        } catch (Exception E) {
                            try {
                                report.put("test", E.getMessage());
                                stats.incFailed();
                            } catch (JSONException ex) {
                                Logger.getLogger(ServiceTest2.class.getName()).log(Level.SEVERE, null, ex);
                            }
                        } finally {
                            synchronized (reports) {
                                reports.put(report);
                            }
                        }
                        return null;
                    };
                }
            });
        }

        JSONObject r = new JSONObject();
        try {
            r.put("target", target.toString());
            r.put("date", new Date());
            r.put("total", stats.getTotal());
            r.put("successful", stats.getSucceeded());
            r.put("failed", stats.getFailed());
            r.put("tests", reports);
        } catch (JSONException ex) {
            ex.printStackTrace();
        }
        return r;
    }

    /**
     *
     */
    static class Stats {

        int succeeded;
        int failed;


        void add(Stats other) {
            succeeded += other.getSucceeded();
            failed += other.getFailed();
        }


        public int getTotal() {
            return succeeded + failed;
        }


        public int getFailed() {
            return failed;
        }


        public int getSucceeded() {
            return succeeded;
        }


        synchronized void incSuccess() {
            succeeded++;
        }


        synchronized void incFailed() {
            failed++;
        }
    }


    JSONObject runtest(JSONObject golden, JSONObject response) throws Exception {
        JSONArray response_result = response.getJSONArray("result");
        JSONArray response_golden = golden.getJSONArray("result");

        Map<String, JSONObject> rmap = JSONUtils.preprocess(response_result);
        Map<String, JSONObject> golmap = JSONUtils.preprocess(response_golden);
        for (String key : rmap.keySet()) {
            JSONObject val = rmap.get(key);
            String url = val.getString("value");
            if (url.startsWith("http") && url.endsWith(key)) {
                response_result.remove(golmap.get(key));
                response_golden.remove(golmap.get(key));
            }
        }
        JSONObject o = new JSONObject();
        o.put("testing", testing);
        switch (testing) {
            case KEYVALUE:
                o.put("result", compareKeyValues(response_result, response_golden));
                break;
            case STRICT:
                o.put("result", compareStrict(response_result, response_golden));
                break;
            case KEYSONLY:
                o.put("result", compareKeys(response_result, response_golden));
                break;
            default:
                o.put("result", "unknown testing config: " + testing);
        }
        return o;
    }


//    static void download(JSONObject resp, File resultFolder, Client client) throws Exception {
//        JSONArray arr = resp.getJSONArray("reports");
//        // download files using the 'q' service.
//        Map<String, JSONObject> rmap = JSONUtils.preprocess(arr);
//        for (String key : rmap.keySet()) {
//            JSONObject val = rmap.get(key);
//            String url = val.getString("value");
//            if (url == null) {
//                throw new Exception("Expecting file in reports: " + key);
//            }
//            if (url.startsWith("http") && url.contains("/q/") && url.endsWith(key)) {
//                if (!resultFolder.exists()) {
//                    resultFolder.mkdirs();
//                }
//                File outFile = new File(resultFolder, key);
//                client.doGET(url, outFile);
//                if (!outFile.exists()) {
//                    throw new Exception("Missing output file: " + key);
//                }
//            }
//        }
//    }
    static JSONObject compareKeyValues(JSONArray res, JSONArray golden) throws Exception {
        Map<String, JSONObject> pres = JSONUtils.preprocess(res);
        Map<String, JSONObject> pgol = JSONUtils.preprocess(golden);
        JSONObject r = new JSONObject();
        for (String name : pgol.keySet()) {
            System.out.println(" .... " + name);
            if (!pres.containsKey(name)) {
                r.accumulate("missing", name);
                continue;
            }
            String resval = pres.get(name).get(ModelDataService.KEY_VALUE).toString();
            String golval = pgol.get(name).get("value").toString();
            if (resval.startsWith("http:") || resval.startsWith("https:") || resval.startsWith("file:")) {
                r.accumulate("ignored", name);
                continue;
            }
            try {
                double dgolval = Double.parseDouble(resval);
                double dresval = Double.parseDouble(golval);
                if (dgolval != dresval) {
                    r.accumulate("differ", "'" + name + "': " + dgolval + " != " + dresval + " \n");
                } else {
                    r.accumulate("ok", name);
                }
            } catch (NumberFormatException E) {
                if (!golval.equals(resval)) {
                    r.accumulate("differ", "'" + name + "': '" + golval + "' != '" + resval + "' \n");
                } else {
                    r.accumulate("ok", name);
                }
            }
        }
        return r;
    }


    static JSONObject compareKeys(JSONArray res, JSONArray golden) throws Exception {
        JSONObject r = new JSONObject();
        Map<String, JSONObject> pres = JSONUtils.preprocess(res);
        Map<String, JSONObject> pgol = JSONUtils.preprocess(golden);
        for (String key : pgol.keySet()) {
            if (!pres.containsKey(key)) {
                r.append("missing", key);
            }
            pres.remove(key);
        }
        if (!pres.keySet().isEmpty()) {
            r.append("extra", pres.keySet().toString());
        }
        return r;
    }


    static JSONObject compareStrict(JSONArray res, JSONArray gol) throws Exception {
        JSONObject o = new JSONObject();
        try {
            JSONAssert.assertEquals(gol.toString(), res.toString(), false);
            o.put("result", "ok");
            return o;
        } catch (AssertionError E) {
            o.put("differ", E.getMessage());
        }
        return o;
    }


    public static JSONArray run(Properties p, String... targets) throws Exception {
        JSONArray summary = new JSONArray();
        for (String target : targets) {
            JSONObject result = new ServiceTest2(new File(target), p).test();
            summary.put(result);
        }
        return summary;
    }


    static String getServicePath(String url) throws URISyntaxException {
//        URIBuilder b = new URIBuilder("http://localhost:8080/csip-example/m/simpleservice/2.0");
        URIBuilder b = new URIBuilder(url);
        return b.getPath().substring(b.getPath().indexOf('/', 1));
    }


    public static void main(String[] args) throws Exception {
////        args = new String[]{"/od/projects/csip-all/csip-example/test/service_tests/all"};
//        args = new String[]{"/od/tmp/csip-testing"};
//        Properties config = new Properties();
//        config.setProperty("concurrency", "1");
//        config.setProperty("verify", "keyvalue");
////        config.setProperty("rounds", "3");
////        config.setProperty("delay", "3000");
//        System.out.println(run(config, args).toString(4).replace("\\/", "/"));        

    }
}