View Javadoc
1   /*
2    * Copyright (C) 2019 Google LLC and others
3    *
4    * This program and the accompanying materials are made available under the
5    * terms of the Eclipse Distribution License v. 1.0 which is available at
6    * https://www.eclipse.org/org/documents/edl-v10.php.
7    *
8    * SPDX-License-Identifier: BSD-3-Clause
9    */
10  
11  package org.eclipse.jgit.internal.storage.file;
12  
13  import static org.eclipse.jgit.lib.Ref.Storage.PACKED;
14  import static org.junit.Assert.assertEquals;
15  import static org.junit.Assert.assertThrows;
16  import static org.junit.Assert.assertTrue;
17  import static org.junit.Assume.assumeFalse;
18  
19  import java.io.File;
20  import java.io.FileNotFoundException;
21  import java.io.IOException;
22  import java.nio.file.Files;
23  import java.nio.file.Path;
24  import java.util.Collections;
25  import java.util.List;
26  import java.util.stream.Collectors;
27  
28  import org.eclipse.jgit.internal.storage.file.FileReftableStack.Segment;
29  import org.eclipse.jgit.internal.storage.reftable.MergedReftable;
30  import org.eclipse.jgit.internal.storage.reftable.RefCursor;
31  import org.eclipse.jgit.lib.Config;
32  import org.eclipse.jgit.lib.ObjectId;
33  import org.eclipse.jgit.lib.ObjectIdRef;
34  import org.eclipse.jgit.lib.Ref;
35  import org.eclipse.jgit.util.FileUtils;
36  import org.eclipse.jgit.util.SystemReader;
37  import org.junit.After;
38  import org.junit.Before;
39  import org.junit.Test;
40  
41  
42  public class FileReftableStackTest {
43  
44  	private static Ref newRef(String name, ObjectId id) {
45  		return new ObjectIdRef.PeeledNonTag(PACKED, name, id);
46  	}
47  
48  	private File reftableDir;
49  
50  	@Before
51  	public void setup() throws Exception {
52  		reftableDir = FileUtils.createTempDir("rtstack", "", null);
53  	}
54  
55  	@After
56  	public void tearDown() throws Exception {
57  		if (reftableDir != null) {
58  			FileUtils.delete(reftableDir, FileUtils.RECURSIVE);
59  		}
60  	}
61  
62  	void writeBranches(FileReftableStack stack, String template, int start,
63  			int N) throws IOException {
64  		for (int i = 0; i < N; i++) {
65  			while (true) {
66  				final long next = stack.getMergedReftable().maxUpdateIndex()
67  						+ 1;
68  
69  				String name = String.format(template,
70  						Integer.valueOf(start + i));
71  				Ref r = newRef(name, ObjectId.zeroId());
72  				boolean ok = stack.addReftable(rw -> {
73  					rw.setMinUpdateIndex(next).setMaxUpdateIndex(next).begin()
74  							.writeRef(r);
75  				});
76  				if (ok) {
77  					break;
78  				}
79  			}
80  		}
81  	}
82  
83  	public void testCompaction(int N) throws Exception {
84  		try (FileReftableStack stack = new FileReftableStack(
85  				new File(reftableDir, "refs"), reftableDir, null,
86  				() -> new Config())) {
87  			writeBranches(stack, "refs/heads/branch%d", 0, N);
88  			MergedReftable table = stack.getMergedReftable();
89  			for (int i = 1; i < N; i++) {
90  				String name = String.format("refs/heads/branch%d",
91  						Integer.valueOf(i));
92  				RefCursor c = table.seekRef(name);
93  				assertTrue(c.next());
94  				assertEquals(ObjectId.zeroId(), c.getRef().getObjectId());
95  			}
96  
97  			List<String> files = Files.list(reftableDir.toPath())
98  					.map(Path::getFileName).map(Path::toString)
99  					.collect(Collectors.toList());
100 			Collections.sort(files);
101 
102 			assertTrue(files.size() < 20);
103 
104 			FileReftableStack.CompactionStats stats = stack.getStats();
105 			assertEquals(0, stats.failed);
106 			assertTrue(stats.attempted < N);
107 			assertTrue(stats.refCount < FileReftableStack.log(N) * N);
108 		}
109 	}
110 
111 	@Test
112 	public void testCompaction9() throws Exception {
113 		testCompaction(9);
114 	}
115 
116 	@Test
117 	public void testCompaction1024() throws Exception {
118 		testCompaction(1024);
119 	}
120 
121 	@SuppressWarnings("resource")
122 	@Test
123 	public void missingReftable() throws Exception {
124 		// Can't delete in-use files on Windows.
125 		assumeFalse(SystemReader.getInstance().isWindows());
126 
127 		try (FileReftableStack stack = new FileReftableStack(
128 				new File(reftableDir, "refs"), reftableDir, null,
129 				() -> new Config())) {
130 			outer: for (int i = 0; i < 10; i++) {
131 				final long next = stack.getMergedReftable().maxUpdateIndex()
132 						+ 1;
133 				String name = String.format("branch%d", Integer.valueOf(i));
134 				Ref r = newRef(name, ObjectId.zeroId());
135 				boolean ok = stack.addReftable(rw -> {
136 					rw.setMinUpdateIndex(next).setMaxUpdateIndex(next).begin()
137 							.writeRef(r);
138 				});
139 				assertTrue(ok);
140 
141 				List<Path> files = Files.list(reftableDir.toPath())
142 						.collect(Collectors.toList());
143 				for (int j = 0; j < files.size(); j++) {
144 					Path f = files.get(j);
145 					Path fileName = f.getFileName();
146 					if (fileName != null
147 							&& fileName.toString().endsWith(".ref")) {
148 						Files.delete(f);
149 						break outer;
150 					}
151 				}
152 			}
153 		}
154 		assertThrows(FileNotFoundException.class,
155 				() -> new FileReftableStack(new File(reftableDir, "refs"),
156 						reftableDir, null, () -> new Config()));
157 	}
158 
159 	@Test
160 	public void testSegments() {
161 		long in[] = { 1024, 1024, 1536, 100, 64, 50, 25, 24 };
162 		List<Segment> got = FileReftableStack.segmentSizes(in);
163 		Segment want[] = { new Segment(0, 3, 10, 3584),
164 				new Segment(3, 5, 6, 164), new Segment(5, 6, 5, 50),
165 				new Segment(6, 8, 4, 49), };
166 		assertEquals(got.size(), want.length);
167 		for (int i = 0; i < want.length; i++) {
168 			assertTrue(want[i].equals(got.get(i)));
169 		}
170 	}
171 
172 	@Test
173 	public void testLog2() throws Exception {
174 		assertEquals(10, FileReftableStack.log(1024));
175 		assertEquals(10, FileReftableStack.log(1025));
176 		assertEquals(10, FileReftableStack.log(2047));
177 	}
178 }