;; Tests for JMX support for Clojure (see also clojure/contrib/jmx.clj)

;; by Stuart Halloway

;; Copyright (c) Stuart Halloway, 2009. All rights reserved.  The use
;; and distribution terms for this software are covered by the Eclipse
;; Public License 1.0 (http://opensource.org/licenses/eclipse-1.0.php)
;; which can be found in the file epl-v10.html at the root of this
;; distribution.  By using this software in any fashion, you are
;; agreeing to be bound by the terms of this license.  You must not
;; remove this notice, or any other, from this software.

(ns clojure.contrib.test-contrib.test-jmx
  (:import javax.management.openmbean.CompositeDataSupport
           [javax.management MBeanAttributeInfo AttributeList]
           [java.util.logging LogManager Logger]
           clojure.contrib.jmx.Bean)
  (:use clojure.test)
  (:require [clojure.contrib [jmx :as jmx]]))


(defn =set [a b]
  (= (set a) (set b)))

(deftest finding-mbeans
  (testing "as-object-name"
           (are [cname object-name]
                (= cname (.getCanonicalName object-name))
                "java.lang:type=Memory" (jmx/as-object-name "java.lang:type=Memory")))
  (testing "mbean-names"
           (are [cnames object-name]
                (= cnames (map #(.getCanonicalName %) object-name))
                ["java.lang:type=Memory"] (jmx/mbean-names "java.lang:type=Memory"))))

; don't know which attributes are common on all JVM platforms. May
; need to change expectations.
(deftest reflecting-on-capabilities
  (are [attr-list mbean-name]
       (= (set attr-list) (set (jmx/attribute-names mbean-name)))
       [:Verbose :ObjectPendingFinalizationCount :HeapMemoryUsage :NonHeapMemoryUsage] "java.lang:type=Memory")
  (are [a b]
       (= (set a) (set b))
       [:gc] (jmx/operation-names "java.lang:type=Memory")))

(deftest raw-reading-attributes
  (let [mem "java.lang:type=Memory"
        log "java.util.logging:type=Logging"]
    (testing "simple scalar attributes"
             (are [a b] (= a b)
                  false (jmx/raw-read mem :Verbose))
             (are [type attr] (instance? type attr)
                  Integer (jmx/raw-read mem :ObjectPendingFinalizationCount)))))

(deftest reading-attributes
  (testing "simple scalar attributes"
           (are [type attr] (instance? type attr)
                Integer (jmx/read "java.lang:type=Memory" :ObjectPendingFinalizationCount)))
  (testing "composite attributes"
           (are [ks attr] (=set ks (keys attr))
                [:used :max :init :committed] (jmx/read "java.lang:type=Memory" :HeapMemoryUsage)))
  (testing "tabular attributes"
           (is (map? (jmx/read "java.lang:type=Runtime" :SystemProperties)))))

(deftest mbean-from-oname
  (are [oname key-names]
       (= (set key-names) (set (keys (jmx/mbean oname))))
       "java.lang:type=Memory" [:Verbose :ObjectPendingFinalizationCount :HeapMemoryUsage :NonHeapMemoryUsage]))

(deftest writing-attributes
  (let [mem "java.lang:type=Memory"]
    (jmx/write! mem :Verbose true)
    (is (true? (jmx/raw-read mem :Verbose)))
    (jmx/write! mem :Verbose false)))

(deftest test-invoke-operations
  (testing "without arguments"
           (jmx/invoke "java.lang:type=Memory" :gc))
  (testing "with arguments"
           (.addLogger (LogManager/getLogManager) (Logger/getLogger "clojure.contrib.test_contrib.test_jmx"))
           (jmx/invoke "java.util.logging:type=Logging" :setLoggerLevel "clojure.contrib.test_contrib.test_jmx" "WARNING")))

(deftest test-jmx->clj
  (testing "it works recursively on maps"
           (let [some-map {:foo (jmx/raw-read "java.lang:type=Memory" :HeapMemoryUsage)}]
             (is (map? (:foo (jmx/jmx->clj some-map))))))
  (testing "it leaves everything else untouched"
           (is (= "foo" (jmx/jmx->clj "foo")))))
  
  
(deftest test-composite-data->map
  (let [data (jmx/raw-read "java.lang:type=Memory" :HeapMemoryUsage)
        prox (jmx/composite-data->map data)]
    (testing "returns a map with keyword keys"
             (is (= (set [:committed :init :max :used]) (set (keys prox)))))))

(deftest test-tabular-data->map
  (let [raw-props (jmx/raw-read "java.lang:type=Runtime" :SystemProperties)
        props (jmx/tabular-data->map raw-props)]
    (are [k] (contains? props k)
         :java.class.path
         :path.separator)))

(deftest test-creating-attribute-infos
  (let [infos (jmx/map->attribute-infos [[:a 1] [:b 2]])
        info (first infos)]
    (testing "generates the right class"
             (is (= (class (into-array MBeanAttributeInfo [])) (class infos))))
    (testing "generates the right instance data"
             (are [result expr] (= result expr)
                  "a" (.getName info)
                  "a" (.getDescription info)))))

(deftest various-beans-are-readable
  (testing "that all java.lang beans can be read without error"
           (doseq [mb (jmx/mbean-names "*:*")]
             (jmx/mbean mb))))

(deftest test-jmx-url
  (testing "creates default url"
           (is (= "service:jmx:rmi:///jndi/rmi://localhost:3000/jmxrmi" (jmx/jmx-url))))
  (testing "creates custom url"
           (is (= "service:jmx:rmi:///jndi/rmi://example.com:4000/jmxrmi" (jmx/jmx-url {:host "example.com" :port 4000})))))

;; ----------------------------------------------------------------------
;; tests for clojure.contrib.jmx.Bean.

(deftest dynamic-mbean-from-compiled-class
  (let [mbean-name "clojure.contrib.test_contrib.test_jmx:name=Foo"]
    (jmx/register-mbean
     (Bean.
      (ref {:string-attribute "a-string"}))
     mbean-name)
    (are [result expr] (= result expr)
         "a-string" (jmx/read mbean-name :string-attribute)
         {:string-attribute "a-string"} (jmx/mbean mbean-name)
         )))

(deftest test-getAttribute
  (doseq [reftype [ref atom agent]]
    (let [state (reftype {:a 1 :b 2})
          bean (Bean. state)]
      (testing (str "accessing values from a " (class state))
               (are [result expr] (= result expr)
                    1 (.getAttribute bean "a"))))))

(deftest test-bean-info
  (let [state (ref {:a 1 :b 2})
        bean (Bean. state)
        info (.getMBeanInfo bean)]
    (testing "accessing info"
             (are [result expr] (= result expr)
                  "clojure.contrib.jmx.Bean" (.getClassName info)))))

(deftest test-getAttributes
  (let [bean (Bean. (ref {:r 5 :d 4}))
        atts (.getAttributes bean (into-array ["r" "d"]))]
    (are [x y] (= x y)
         AttributeList (class atts)
         [5 4] (seq atts))))

(deftest test-guess-attribute-typename
  (are [x y] (= x (jmx/guess-attribute-typename y))
       "int" 10
       "boolean" false
       "java.lang.String" "foo"
       "long" (long 10)))