diff --git a/Data/Text/ICU.hs b/Data/Text/ICU.hs
--- a/Data/Text/ICU.hs
+++ b/Data/Text/ICU.hs
@@ -58,6 +58,7 @@
     -- $collate
     , Collator
     , collator
+    , collatorWith
     , collate
     , collateIter
     , sortKey
@@ -133,10 +134,10 @@
 --   third word in the document).
 --
 -- The 'Breaker' type was designed to support these kinds of
--- tasks. 
+-- tasks.
 --
 -- For the impure boundary analysis API (which is richer, but less
--- easy to use than the pure API), see the 'Data.Text.ICU.Break'
+-- easy to use than the pure API), see the "Data.Text.ICU.Break"
 -- module.  The impure API supports some uses that may be less
 -- efficient via the pure API, including:
 --
@@ -150,7 +151,7 @@
 -- $collate
 --
 -- For the impure collation API (which is richer, but less easy to
--- use than the pure API), see the 'Data.Text.ICU.Collate'
+-- use than the pure API), see the "Data.Text.ICU.Collate"
 -- module.
 
 -- $group
diff --git a/Data/Text/ICU/Collate.hsc b/Data/Text/ICU/Collate.hsc
--- a/Data/Text/ICU/Collate.hsc
+++ b/Data/Text/ICU/Collate.hsc
@@ -30,6 +30,7 @@
     , getAttribute
     , setAttribute
     , sortKey
+    , clone
     , freeze
     ) where
 
@@ -289,11 +290,17 @@
 -- Subsequent changes to the 'MCollator' will not affect the state of
 -- the returned 'Collator'.
 freeze :: MCollator -> IO Collator
-freeze c = do
+freeze = fmap C . clone
+
+-- | Make a copy of a mutable 'MCollator'.
+-- Subsequent changes to the input 'MCollator' will not affect the state of
+-- the returned 'MCollator'.
+clone :: MCollator -> IO MCollator
+clone c = do
   p <- withCollator c $ \cptr ->
     with (#const U_COL_SAFECLONE_BUFFERSIZE)
       (handleError . ucol_safeClone cptr nullPtr)
-  C `fmap` wrap p
+  wrap p
 
 foreign import ccall unsafe "hs_text_icu.h __hs_ucol_open" ucol_open
     :: CString -> Ptr UErrorCode -> IO (Ptr UCollator)
diff --git a/Data/Text/ICU/Collate/Pure.hs b/Data/Text/ICU/Collate/Pure.hs
--- a/Data/Text/ICU/Collate/Pure.hs
+++ b/Data/Text/ICU/Collate/Pure.hs
@@ -13,7 +13,7 @@
 -- libraries.
 --
 -- For the impure collation API (which is richer, but less easy to
--- use), see the 'Data.Text.ICU.CollateO' module.
+-- use), see the "Data.Text.ICU.Collate" module.
 
 module Data.Text.ICU.Collate.Pure
     (
@@ -21,12 +21,14 @@
     -- $api
       Collator
     , collator
+    , collatorWith
     , collate
     , collateIter
     , sortKey
     , uca
     ) where
 
+import Control.Monad (forM_)
 import Data.ByteString (ByteString)
 import Data.Text (Text)
 import Data.Text.ICU.Collate.Internal (Collator(..))
@@ -36,13 +38,20 @@
 
 -- $api
 --
-                         
+
 -- | Create an immutable 'Collator' for comparing strings.
 --
 -- If 'Root' is passed as the locale, UCA collation rules will be
 -- used.
 collator :: LocaleName -> Collator
 collator loc = unsafePerformIO $ C `fmap` IO.open loc
+
+-- | Create an immutable 'Collator' with the given 'Attribute's.
+collatorWith :: LocaleName -> [IO.Attribute] -> Collator
+collatorWith loc atts = unsafePerformIO $ do
+  mc <- IO.open loc
+  forM_ atts $ IO.setAttribute mc
+  return (C mc)
 
 -- | Compare two strings.
 collate :: Collator -> Text -> Text -> Ordering
diff --git a/Data/Text/ICU/Convert.hs b/Data/Text/ICU/Convert.hs
--- a/Data/Text/ICU/Convert.hs
+++ b/Data/Text/ICU/Convert.hs
@@ -120,7 +120,7 @@
   unsafePerformIO . unsafeUseAsCStringLen bs $ \(sptr, slen) ->
     withConverter cnv $ \cptr -> do
       let capacity = slen * 2
-      allocaArray capacity $ \tptr -> 
+      allocaArray capacity $ \tptr ->
         fromPtr tptr =<< (fmap fromIntegral . handleError $
                           ucnv_toUChars cptr tptr (fromIntegral capacity) sptr
                                         (fromIntegral slen))
diff --git a/Data/Text/ICU/Error/Internal.hsc b/Data/Text/ICU/Error/Internal.hsc
--- a/Data/Text/ICU/Error/Internal.hsc
+++ b/Data/Text/ICU/Error/Internal.hsc
@@ -37,7 +37,7 @@
 #include <unicode/utypes.h>
 
 type UErrorCode = CInt
-    
+
 -- | ICU error type.  This is an instance of the 'Exception' type
 -- class.  A value of this type may be thrown as an exception by most
 -- ICU functions.
diff --git a/Data/Text/ICU/Internal.hsc b/Data/Text/ICU/Internal.hsc
--- a/Data/Text/ICU/Internal.hsc
+++ b/Data/Text/ICU/Internal.hsc
@@ -87,7 +87,7 @@
                 -- and the root resource, see
                 -- <http://userguide.icu-project.org/locale/resources>.
                 | Locale String -- ^ A specific locale.
-                | Current       -- ^ The program's current locale. 
+                | Current       -- ^ The program's current locale.
                   deriving (Eq, Ord, Read, Show)
 
 instance IsString LocaleName where
diff --git a/Data/Text/ICU/Iterator.hs b/Data/Text/ICU/Iterator.hs
--- a/Data/Text/ICU/Iterator.hs
+++ b/Data/Text/ICU/Iterator.hs
@@ -61,7 +61,8 @@
 {-# INLINE fromText #-}
 
 -- | Construct a 'CharIterator' from a Unicode string encoded as a
--- UTF-8 'ByteString'.
+-- UTF-8 'ByteString'. The validity of the encoded string is *not*
+-- checked.
 fromUtf8 :: ByteString -> CharIterator
 fromUtf8 = CIUTF8
 {-# INLINE fromUtf8 #-}
diff --git a/Data/Text/ICU/Normalize.hsc b/Data/Text/ICU/Normalize.hsc
--- a/Data/Text/ICU/Normalize.hsc
+++ b/Data/Text/ICU/Normalize.hsc
@@ -192,7 +192,7 @@
     | NFKC   -- ^ Compatibility decomposition followed by canonical composition.
     | FCD    -- ^ \"Fast C or D\" form.
       deriving (Eq, Show, Enum, Typeable)
-                       
+
 toNM :: NormalizationMode -> UNormalizationMode
 toNM None = #const UNORM_NONE
 toNM NFD  = #const UNORM_NFD
@@ -209,8 +209,8 @@
   in handleOverflowError (fromIntegral slen)
      (\dptr dlen -> unorm_normalize sptr slen' mode' 0 dptr (fromIntegral dlen))
      (\dptr dlen -> fromPtr (castPtr dptr) (fromIntegral dlen))
-    
-      
+
+
 -- | Perform an efficient check on a string, to quickly determine if
 -- the string is in a particular normalization form.
 --
diff --git a/Data/Text/ICU/Regex.hs b/Data/Text/ICU/Regex.hs
--- a/Data/Text/ICU/Regex.hs
+++ b/Data/Text/ICU/Regex.hs
@@ -63,7 +63,7 @@
 import Foreign.Marshal.Alloc (alloca)
 import Foreign.Storable (peek)
 import System.IO.Unsafe (unsafePerformIO)
-                   
+
 instance Show Regex where
     show re = "Regex " ++ show (pattern re)
 
diff --git a/Data/Text/ICU/Text.hs b/Data/Text/ICU/Text.hs
--- a/Data/Text/ICU/Text.hs
+++ b/Data/Text/ICU/Text.hs
@@ -22,11 +22,10 @@
 import Data.Int (Int32)
 import Data.Text (Text)
 import Data.Text.Foreign (fromPtr, useAsPtr)
-import Data.Text.ICU.Error.Internal (UErrorCode, handleError, handleOverflowError)
+import Data.Text.ICU.Error.Internal (UErrorCode, handleOverflowError)
 import Data.Text.ICU.Internal (LocaleName, UChar, withLocaleName)
 import Data.Word (Word32)
 import Foreign.C.String (CString)
-import Foreign.Marshal.Array (allocaArray)
 import Foreign.Ptr (Ptr, castPtr)
 import System.IO.Unsafe (unsafePerformIO)
 
@@ -48,14 +47,9 @@
 toCaseFold excludeI s = unsafePerformIO .
   useAsPtr s $ \sptr slen -> do
     let opts = fromIntegral . fromEnum $ excludeI
-        go len = allocaArray len $ \dptr -> do
-          n <- fmap fromIntegral . handleError $
-               u_strFoldCase dptr (fromIntegral len) sptr
-                                  (fromIntegral slen) opts
-          if n > len
-            then go n
-            else fromPtr dptr (fromIntegral n)
-    go (fromIntegral slen)
+    handleOverflowError (fromIntegral slen)
+        (\dptr dlen -> u_strFoldCase dptr dlen sptr (fromIntegral slen) opts)
+        (\dptr dlen -> fromPtr (castPtr dptr) (fromIntegral dlen))
 
 type CaseMapper = Ptr UChar -> Int32 -> Ptr UChar -> Int32 -> CString
                 -> Ptr UErrorCode -> IO Int32
diff --git a/benchmarks/Breaker.hs b/benchmarks/Breaker.hs
new file mode 100644
--- /dev/null
+++ b/benchmarks/Breaker.hs
@@ -0,0 +1,50 @@
+-- Estimate the time difference between creating a breaker.
+
+{-# LANGUAGE OverloadedStrings #-}
+
+import Control.Monad
+import qualified Data.Text as T
+import Data.Text.IO as T
+import Data.Text.ICU.Break as IO
+import Data.Text.ICU as ICU
+import System.Environment
+
+consume b = go
+  where
+    go = do
+      m <- next b
+      case m of
+        Nothing -> return ()
+        Just _ -> go
+
+manyBreakers (t:ts) = do
+  b <- IO.breakWord "en" t
+  consume b
+  manyBreakers ts
+manyBreakers _ = return ()
+
+oneBreaker ts = do
+  b <- IO.breakWord "en" ""
+  forM_ ts $ \t -> do
+    setText b t
+    consume b
+
+cloneBreakers ts = do
+  b <- IO.breakWord "en" ""
+  forM_ ts $ \t -> do
+    b' <- clone b
+    setText b' t
+    consume b'
+
+pureBreaker ts = do
+  let b = ICU.breakWord "en"
+  forM_ ts $ \t -> length (breaks b t) `seq` return ()
+
+main = do
+  (kind:files) <- getArgs
+  let act = case kind of
+              "one" -> oneBreaker
+              "many" -> manyBreakers
+              "clone" -> cloneBreakers
+              "pure" -> pureBreaker
+  forM_ files $ \f -> T.readFile f >>= act . T.lines
diff --git a/tests/Tests.hs b/tests/Tests.hs
new file mode 100644
--- /dev/null
+++ b/tests/Tests.hs
@@ -0,0 +1,8 @@
+module Main (main) where
+
+import Test.Framework (defaultMain)
+
+import qualified Properties
+
+main :: IO ()
+main = defaultMain [Properties.tests]
diff --git a/tests/benchmarks/Breaker.hs b/tests/benchmarks/Breaker.hs
deleted file mode 100644
--- a/tests/benchmarks/Breaker.hs
+++ /dev/null
@@ -1,50 +0,0 @@
--- Estimate the time difference between creating a breaker.
-
-{-# LANGUAGE OverloadedStrings #-}
-
-import Control.Monad
-import qualified Data.Text as T
-import Data.Text.IO as T
-import Data.Text.ICU.Break as IO
-import Data.Text.ICU as ICU
-import System.Environment
-
-consume b = go
-  where
-    go = do
-      m <- next b
-      case m of
-        Nothing -> return ()
-        Just _ -> go
-
-manyBreakers (t:ts) = do
-  b <- IO.breakWord "en" t
-  consume b
-  manyBreakers ts
-manyBreakers _ = return ()
-
-oneBreaker ts = do
-  b <- IO.breakWord "en" ""
-  forM_ ts $ \t -> do
-    setText b t
-    consume b
-
-cloneBreakers ts = do
-  b <- IO.breakWord "en" ""
-  forM_ ts $ \t -> do
-    b' <- clone b
-    setText b' t
-    consume b'
-
-pureBreaker ts = do
-  let b = ICU.breakWord "en"
-  forM_ ts $ \t -> length (breaks b t) `seq` return ()
-
-main = do
-  (kind:files) <- getArgs
-  let act = case kind of
-              "one" -> oneBreaker
-              "many" -> manyBreakers
-              "clone" -> cloneBreakers
-              "pure" -> pureBreaker
-  forM_ files $ \f -> T.readFile f >>= act . T.lines
diff --git a/text-icu.cabal b/text-icu.cabal
--- a/text-icu.cabal
+++ b/text-icu.cabal
@@ -1,8 +1,8 @@
 name:           text-icu
-version:        0.6.3.5
+version:        0.6.3.6
 synopsis:       Bindings to the ICU library
-homepage:       https://bitbucket.org/bos/text-icu
-bug-reports:    https://bitbucket.org/bos/text-icu/issues
+homepage:       https://github.com/bos/text-icu
+bug-reports:    https://github.com/bos/text-icu/issues
 description:
   Haskell bindings to the International Components for Unicode (ICU)
   libraries.  These libraries provide robust and full-featured Unicode
@@ -30,19 +30,21 @@
   .
   * Regular expression search and replace.
 maintainer:     Bryan O'Sullivan <bos@serpentine.com>
-copyright:      2009, 2010 Bryan O'Sullivan
+copyright:      2009-2013 Bryan O'Sullivan
 category:       Data, Text
 license:        BSD3
 license-file:   LICENSE
 build-type:     Simple
-cabal-version:  >= 1.6
+cabal-version:  >= 1.10
 extra-source-files:
-  README.markdown include/hs_text_icu.h tests/benchmarks/Breaker.hs
+  README.markdown include/hs_text_icu.h benchmarks/Breaker.hs
 
 library
-  build-depends:     base < 5, bytestring, text >= 0.9.1.0 && <= 0.12.0.0
-  if impl(ghc >= 6.10)
-    build-depends:   base >= 4
+  default-language:  Haskell98
+  build-depends:
+    base >= 4 && < 5,
+    bytestring,
+    text >= 0.9.1.0
 
   exposed-modules:
       Data.Text.ICU
@@ -79,10 +81,35 @@
   if impl(ghc >= 6.8)
     ghc-options: -fwarn-tabs
 
-source-repository head
-  type:     mercurial
-  location: https://bitbucket.org/bos/text-icu
+test-suite tests
+  default-language: Haskell98
+  type:             exitcode-stdio-1.0
+  hs-source-dirs:   tests
+  main-is:          Tests.hs
 
+  ghc-options:
+    -Wall -threaded -O0 -rtsopts
+
+  build-depends:
+    HUnit >= 1.2,
+    QuickCheck >= 2.4,
+    array,
+    base >= 4 && < 5,
+    bytestring,
+    deepseq,
+    directory,
+    ghc-prim,
+    random,
+    test-framework >= 0.4,
+    test-framework-hunit >= 0.2,
+    test-framework-quickcheck2 >= 0.2,
+    text,
+    text-icu
+
 source-repository head
   type:     git
   location: https://github.com/bos/text-icu
+
+source-repository head
+  type:     mercurial
+  location: https://bitbucket.org/bos/text-icu
