Repository: clj-commons/aleph
Pull request #670 (2023-02-07 22:59:29)

Randomize UDP ports
## Description

Since an `UDP` socket doesn't implement the `AlephServer` protocol, the `port` function is unavailable.
Here is a quick solution to stabilize those tests.


Patch:
diff --git a/deps.edn b/deps.edn
index eab2156..c6e7fcf 100644
--- a/deps.edn
+++ b/deps.edn
@@ -4,27 +4,27 @@
  {org.clojure/tools.logging
   {:mvn/version "1.1.0", :exclusions [org.clojure/clojure]},
   io.netty/netty-transport-native-kqueue$osx-x86_64
-  #:mvn{:version "4.1.85.Final"},
+  #:mvn{:version "4.1.87.Final"},
   io.netty.incubator/netty-incubator-transport-native-io_uring$linux-aarch_64
   #:mvn{:version "0.0.16.Final"},
-  io.netty/netty-handler-proxy #:mvn{:version "4.1.85.Final"},
-  manifold/manifold #:mvn{:version "0.2.4"},
-  io.netty/netty-codec #:mvn{:version "4.1.85.Final"},
+  io.netty/netty-handler-proxy #:mvn{:version "4.1.87.Final"},
+  manifold/manifold #:mvn{:version "0.3.0"},
+  io.netty/netty-codec #:mvn{:version "4.1.87.Final"},
   io.netty/netty-transport-native-epoll$linux-aarch_64
-  #:mvn{:version "4.1.85.Final"},
-  org.clj-commons/dirigiste #:mvn{:version "1.0.1"},
-  io.netty/netty-handler #:mvn{:version "4.1.85.Final"},
+  #:mvn{:version "4.1.87.Final"},
+  org.clj-commons/dirigiste #:mvn{:version "1.0.3"},
+  io.netty/netty-handler #:mvn{:version "4.1.87.Final"},
   org.clj-commons/primitive-math #:mvn{:version "1.0.0"},
   io.netty/netty-transport-native-epoll$linux-x86_64
-  #:mvn{:version "4.1.85.Final"},
+  #:mvn{:version "4.1.87.Final"},
   io.netty.incubator/netty-incubator-transport-native-io_uring$linux-x86_64
   #:mvn{:version "0.0.16.Final"},
-  io.netty/netty-transport #:mvn{:version "4.1.85.Final"},
+  io.netty/netty-transport #:mvn{:version "4.1.87.Final"},
   org.clj-commons/byte-streams #:mvn{:version "0.3.1"},
-  io.netty/netty-codec-http #:mvn{:version "4.1.85.Final"},
+  io.netty/netty-codec-http #:mvn{:version "4.1.87.Final"},
   potemkin/potemkin #:mvn{:version "0.4.5"},
-  io.netty/netty-resolver #:mvn{:version "4.1.85.Final"},
-  io.netty/netty-resolver-dns #:mvn{:version "4.1.85.Final"}},
+  io.netty/netty-resolver #:mvn{:version "4.1.87.Final"},
+  io.netty/netty-resolver-dns #:mvn{:version "4.1.87.Final"}},
  :aliases
  {:lein2deps
   {:deps
diff --git a/project.clj b/project.clj
index c4a4690..c24641a 100644
--- a/project.clj
+++ b/project.clj
@@ -1,5 +1,5 @@
 ;; you'll need to run the script at `deps/lein-to-deps` after changing any dependencies
-(def netty-version "4.1.85.Final")
+(def netty-version "4.1.87.Final")
 
 (defproject aleph (or (System/getenv "PROJECT_VERSION") "0.6.0")
   :description "A framework for asynchronous communication"
@@ -8,9 +8,9 @@
   :url "https://github.com/clj-commons/aleph"
   :license {:name "MIT License"}
   :dependencies [[org.clojure/tools.logging "1.1.0" :exclusions [org.clojure/clojure]]
-                 [org.clj-commons/dirigiste "1.0.1"]
-                 [manifold "0.2.4"]
+                 [manifold "0.3.0"]
                  [org.clj-commons/byte-streams "0.3.1"]
+                 [org.clj-commons/dirigiste "1.0.3"]
                  [org.clj-commons/primitive-math "1.0.0"]
                  [potemkin "0.4.5"]
                  [io.netty/netty-transport ~netty-version]
diff --git a/src/aleph/http/multipart.clj b/src/aleph/http/multipart.clj
index 81c779d..e74b741 100644
--- a/src/aleph/http/multipart.clj
+++ b/src/aleph/http/multipart.clj
@@ -142,29 +142,23 @@
 
 (defn- encode-file-upload
   "Encode the `content` as a `FileUpload`."
-  [^HttpPostRequestEncoder encoder {:keys [part-name name content mime-type charset]}]
-  (let [filename  (.getName ^File content)
-        name      (or name filename)
-        mime-type (or mime-type
-                      (URLConnection/guessContentTypeFromName filename))
+  [^HttpPostRequestEncoder encoder {:keys [part-name name file-name content mime-type charset]}]
+  (let [file-name    (or file-name (.getName ^File content))
+        name         (or part-name name file-name)
+        mime-type    (or mime-type
+                         (URLConnection/guessContentTypeFromName file-name))
         content-type (mime-type-descriptor mime-type charset)]
-    (.addBodyFileUpload encoder
-                        (or part-name name)
-                        ;; Netty's multipart encoder ignores empty strings here
-                        (or filename "")
-                        content
-                        content-type
-                        false)))
+    (.addBodyFileUpload encoder name file-name content content-type false)))
 
 (defn- encode-memory-file-upload
   "Encode the `content` as a `MemoryFileUpload`."
-  [^HttpPostRequestEncoder encoder {:keys [part-name name content mime-type charset]}]
+  [^HttpPostRequestEncoder encoder {:keys [part-name name file-name content mime-type charset]}]
   (let [content-type (mime-type-descriptor mime-type charset)
         charset      (ensure-charset charset)
-        filename     (str (UUID/randomUUID))
+        file-name    (or file-name (str (UUID/randomUUID)))
         content      (netty/to-byte-buf content)
         size         (.readableBytes content)
-        file-upload  (doto (MemoryFileUpload. (or part-name name) filename content-type "binary" charset size)
+        file-upload  (doto (MemoryFileUpload. (or part-name name) file-name content-type "binary" charset size)
                        (.addContent (netty/to-byte-buf content) true))]
     (.addBodyHttpData encoder file-upload)))
 
@@ -181,12 +175,12 @@
 
 (defn ^:no-doc encode-request [^DefaultHttpRequest req parts]
   (let [^HttpPostRequestEncoder encoder (HttpPostRequestEncoder. req true)]
-    (doseq [{:keys [content mime-type] :as part} parts]
+    (doseq [{:keys [content mime-type file-name] :as part} parts]
       (cond
         (instance? File content)
         (encode-file-upload encoder part)
 
-        (some? mime-type)
+        (or (some? mime-type) (some? file-name))
         (encode-memory-file-upload encoder part)
 
         :else


Test patch:
diff --git a/test/aleph/http/multipart_test.clj b/test/aleph/http/multipart_test.clj
index 41b9a58..eeff5cd 100644
--- a/test/aleph/http/multipart_test.clj
+++ b/test/aleph/http/multipart_test.clj
@@ -160,7 +160,10 @@
              :charset "ISO-8859-1"}
             {:part-name "#6-bytes-with-mime-type"
              :mime-type "text/plain"
-             :content (.getBytes "CONTENT3" "UTF-8")}])
+             :content (.getBytes "CONTENT3" "UTF-8")}
+            {:part-name "#7-bytes-with-file-name"
+             :file-name "data.txt"
+             :content (.getBytes "CONTENT4" "UTF-8")}])
 
 (defn echo-handler [{:keys [body]}]
   {:status 200
@@ -251,7 +254,7 @@
              body         (-> (:body resp) bs/to-string read-string)
              encoded-data (:encoded-data body)
              parts-resp   (-> body :parts vec)]
-         (is (= 7 (count parts-resp)))
+         (is (= 8 (count parts-resp)))
 
          ;; part-names
          (is (= (map :part-name parts)
@@ -274,7 +277,11 @@
          ;; mime-type + memory data
          (is (re-find #"content-disposition: form-data; name=\"#6-bytes-with-mime-type\"; filename=\".*\"\r\ncontent-length: 8\r\ncontent-type: text/plain\r\ncontent-transfer-encoding: binary\r\n\r\nCONTENT3\r\n" encoded-data))
          (is (uuid? (parse-uuid (get-in parts-resp [6 :name]))))
-         (is (= "text/plain" (get-in parts-resp [6 :mime-type]))))
+         (is (= "text/plain" (get-in parts-resp [6 :mime-type])))
+
+         (is (re-find #"content-disposition: form-data; name=\"#7-bytes-with-file-name\"; filename=\"data\.txt\"\r\ncontent-length: 8\r\ncontent-type: application/octet-stream\r\ncontent-transfer-encoding: binary\r\n\r\nCONTENT4\r\n" encoded-data))
+         (is (= "data.txt" (get-in parts-resp [7 :name])))
+         (is (= "application/octet-stream" (get-in parts-resp [7 :mime-type]))))
 
        (finally (.close ^java.io.Closeable s))))))
 
diff --git a/test/aleph/udp_test.clj b/test/aleph/udp_test.clj
index 33e4986..0bb4c4c 100644
--- a/test/aleph/udp_test.clj
+++ b/test/aleph/udp_test.clj
@@ -3,26 +3,40 @@
    [aleph.netty :as netty]
    [aleph.udp :as udp]
    [clj-commons.byte-streams :as bs]
-   [clojure.test :refer [deftest testing is]]
-   [manifold.stream :as s]))
+   [clojure.test :refer [deftest testing is use-fixtures]]
+   [manifold.stream :as s])
+  (:import
+   (java.net ServerSocket)))
 
 (netty/leak-detector-level! :paranoid)
 
+(def ^:dynamic *port* nil)
+
+(defn rand-port []
+  (with-open [socket (ServerSocket. 0)]
+    (.getLocalPort socket)))
+
+(defn random-port-fixture [f]
+  (binding [*port* (rand-port)]
+    (f)))
+
+(use-fixtures :each random-port-fixture)
+
 (deftest test-echo
-  (let [s @(udp/socket {:port 10001})]
-    (s/put! s {:host "localhost", :port 10001, :message "foo"})
+  (let [s @(udp/socket {:port *port*})]
+    (s/put! s {:host "localhost", :port *port*, :message "foo"})
     (is (= "foo"
            (bs/to-string
-             (:message
-              @(s/take! s)))))
+            (:message
+             @(s/take! s)))))
     (s/close! s)))
 
 (deftest test-transport
   (testing "epoll"
     (try
-      (let [s @(udp/socket {:port 10001 :transport :epoll})]
+      (let [s @(udp/socket {:port *port* :transport :epoll})]
         (try
-          (s/put! s {:host "localhost", :port 10001, :message "foo"})
+          (s/put! s {:host "localhost", :port *port*, :message "foo"})
           (is (= "foo"
                  (bs/to-string
                   (:message
@@ -34,9 +48,9 @@
 
   (testing "kqueue"
     (try
-      (let [s @(udp/socket {:port 10001 :transport :kqueue})]
+      (let [s @(udp/socket {:port *port* :transport :kqueue})]
         (try
-          (s/put! s {:host "localhost", :port 10001, :message "foo"})
+          (s/put! s {:host "localhost", :port *port*, :message "foo"})
           (is (= "foo"
                  (bs/to-string
                   (:message
@@ -48,9 +62,9 @@
 
   (testing "io-uring"
     (try
-      (let [s @(udp/socket {:port 10001 :transport :io-uring})]
+      (let [s @(udp/socket {:port *port* :transport :io-uring})]
         (try
-          (s/put! s {:host "localhost", :port 10001, :message "foo"})
+          (s/put! s {:host "localhost", :port *port*, :message "foo"})
           (is (= "foo"
                  (bs/to-string
                   (:message