summaryrefslogtreecommitdiff
path: root/indra/test/namedtempfile.h
blob: 7d59cad32c25618934b9f32b392d7c162170e3c3 (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
/**
 * @file   namedtempfile.h
 * @author Nat Goodspeed
 * @date   2012-01-13
 * @brief  NamedTempFile class for tests that need disk files as fixtures.
 * 
 * $LicenseInfo:firstyear=2012&license=viewerlgpl$
 * Copyright (c) 2012, Linden Research, Inc.
 * $/LicenseInfo$
 */

#if ! defined(LL_NAMEDTEMPFILE_H)
#define LL_NAMEDTEMPFILE_H

#include "llerror.h"
#include "llapr.h"
#include "apr_file_io.h"
#include <string>
#include <boost/function.hpp>
#include <boost/phoenix/core/argument.hpp>
#include <boost/phoenix/operator/bitwise.hpp>
#include <boost/noncopyable.hpp>
#include <iostream>
#include <sstream>

/**
 * Create a text file with specified content "somewhere in the
 * filesystem," cleaning up when it goes out of scope.
 */
class NamedTempFile: public boost::noncopyable
{
    LOG_CLASS(NamedTempFile);
public:
    NamedTempFile(const std::string& pfx, const std::string& content, apr_pool_t* pool=gAPRPoolp):
        mPool(pool)
    {
        createFile(pfx, boost::phoenix::placeholders::arg1 << content);
    }

    // Disambiguate when passing string literal
    NamedTempFile(const std::string& pfx, const char* content, apr_pool_t* pool=gAPRPoolp):
        mPool(pool)
    {
        createFile(pfx, boost::phoenix::placeholders::arg1 << content);
    }

    // Function that accepts an ostream ref and (presumably) writes stuff to
    // it, e.g.:
    // (boost::phoenix::placeholders::arg1 << "the value is " << 17 << '\n')
    typedef boost::function<void(std::ostream&)> Streamer;

    NamedTempFile(const std::string& pfx, const Streamer& func, apr_pool_t* pool=gAPRPoolp):
        mPool(pool)
    {
        createFile(pfx, func);
    }

    virtual ~NamedTempFile()
    {
        ll_apr_assert_status(apr_file_remove(mPath.c_str(), mPool));
    }

    virtual std::string getName() const { return mPath; }

    void peep()
    {
        std::cout << "File '" << mPath << "' contains:\n";
        std::ifstream reader(mPath.c_str());
        std::string line;
        while (std::getline(reader, line))
            std::cout << line << '\n';
        std::cout << "---\n";
    }

protected:
    void createFile(const std::string& pfx, const Streamer& func)
    {
        // Create file in a temporary place.
        const char* tempdir = NULL;
        ll_apr_assert_status(apr_temp_dir_get(&tempdir, mPool));

        // Construct a temp filename template in that directory.
        char *tempname = NULL;
        ll_apr_assert_status(apr_filepath_merge(&tempname,
                                                tempdir,
                                                (pfx + "XXXXXX").c_str(),
                                                0,
                                                mPool));

        // Create a temp file from that template.
        apr_file_t* fp = NULL;
        ll_apr_assert_status(apr_file_mktemp(&fp,
                                             tempname,
                                             APR_CREATE | APR_WRITE | APR_EXCL,
                                             mPool));
        // apr_file_mktemp() alters tempname with the actual name. Not until
        // now is it valid to capture as our mPath.
        mPath = tempname;

        // Write desired content.
        std::ostringstream out;
        // Stream stuff to it.
        func(out);

        std::string data(out.str());
        apr_size_t writelen(data.length());
        ll_apr_assert_status(apr_file_write(fp, data.c_str(), &writelen));
        ll_apr_assert_status(apr_file_close(fp));
        llassert_always(writelen == data.length());
    }

    std::string mPath;
    apr_pool_t* mPool;
};

/**
 * Create a NamedTempFile with a specified filename extension. This is useful
 * when, for instance, you must be able to use the file in a Python import
 * statement.
 *
 * A NamedExtTempFile actually has two different names. We retain the original
 * no-extension name as a placeholder in the temp directory to ensure
 * uniqueness; to that we link the name plus the desired extension. Naturally,
 * both must be removed on destruction.
 */
class NamedExtTempFile: public NamedTempFile
{
    LOG_CLASS(NamedExtTempFile);
public:
    NamedExtTempFile(const std::string& ext, const std::string& content, apr_pool_t* pool=gAPRPoolp):
        NamedTempFile(remove_dot(ext), content, pool),
        mLink(mPath + ensure_dot(ext))
    {
        linkto(mLink);
    }

    // Disambiguate when passing string literal
    NamedExtTempFile(const std::string& ext, const char* content, apr_pool_t* pool=gAPRPoolp):
        NamedTempFile(remove_dot(ext), content, pool),
        mLink(mPath + ensure_dot(ext))
    {
        linkto(mLink);
    }

    NamedExtTempFile(const std::string& ext, const Streamer& func, apr_pool_t* pool=gAPRPoolp):
        NamedTempFile(remove_dot(ext), func, pool),
        mLink(mPath + ensure_dot(ext))
    {
        linkto(mLink);
    }

    virtual ~NamedExtTempFile()
    {
        ll_apr_assert_status(apr_file_remove(mLink.c_str(), mPool));
    }

    // Since the caller has gone to the trouble to create the name with the
    // extension, that should be the name we return. In this class, mPath is
    // just a placeholder to ensure that future createFile() calls won't
    // collide.
    virtual std::string getName() const { return mLink; }

    static std::string ensure_dot(const std::string& ext)
    {
        if (ext.empty())
        {
            // What SHOULD we do when the caller makes a point of using
            // NamedExtTempFile to generate a file with a particular
            // extension, then passes an empty extension? Use just "."? That
            // sounds like a Bad Idea, especially on Windows. Treat that as a
            // coding error.
            LL_ERRS("NamedExtTempFile") << "passed empty extension" << LL_ENDL;
        }
        if (ext[0] == '.')
        {
            return ext;
        }
        return std::string(".") + ext;
    }

    static std::string remove_dot(const std::string& ext)
    {
        std::string::size_type found = ext.find_first_not_of(".");
        if (found == std::string::npos)
        {
            return ext;
        }
        return ext.substr(found);
    }

private:
    void linkto(const std::string& path)
    {
        // This method assumes that since mPath (without extension) is
        // guaranteed by apr_file_mktemp() to be unique, then (mPath + any
        // extension) is also unique. This is likely, though not guaranteed:
        // files could be created in the same temp directory other than by
        // this class.
        ll_apr_assert_status(apr_file_link(mPath.c_str(), path.c_str()));
    }

    std::string mLink;
};

#endif /* ! defined(LL_NAMEDTEMPFILE_H) */